2020-06-11 18:03:35
Author: Cedric Huchuan Xia (email , github )
Affiliation: Penn Lifespan Informatics and Neuroimaging Center (PennLINC )
This GPS Footprinting project is inspired by Finn et al. Nat Neuro (2015) and Kaufmann et al. Nat Neuro (2015) . These authors studied how personal differences in functional brain connectivity can identify individuals, mature during development, alter in neuropsychiatric illness, and differ between genders. The authors referred to these individual differences as brain fingerprinting .
Here, we apply the fingerprinting technique to highly sampled GPS data in a clinical sample of youth. We are interested in how their individual mobility patterns can distinguish one another’s identity, differ between genders, and alter in psychopathological groups. In other words, can our footprint tell us apart and something about our behavior?
GPS data preprocessing was performed according to Ian Barnett’s imputation algorithm , originally published in Biostatistics (2020) .
1. Setup Environment
require(ggplot2)
require(summarytools)
require(cowplot)
require(caret)
require(corrplot)
require(RColorBrewer)
require(vembedr)
require(Rmisc)
require(varian)
require(patchwork)
require(plotly)
require(Metrics)
require(dplyr)
require(ggpubr)
require(mosaic)
require(openxlsx)
source('~/Documents/GitHub/SmartphoneSensorPipeline/Extra/plotting_functions.R')
project_path = "~/Documents/xia_gps/"
data_path = file.path(project_path,"beiwe_output_043020")
gps_df_path = file.path(data_path,"Processed_Data/Group/feature_matrix.txt")
2. GPS Features
The current data has 41 subjects, consisting of 3317 total days, and 15 GPS features. To conserve battery life, a subject’s GPS coordinates were tracked for in a 2-min-on and 18-min-off cycle everyday using their own mobile device via the Beiwe platform . We used Ian Barnett’s algorithm to impute the missing data during the off cycles. Assuming no more data was missing due to various factors, one would generate 144 mins of GPS data per day, or 10% of total minutes in a day.
As you can see from the figure below, we can nicely reconstruct an individuals’ mobility trajectory from these data.
Figure 1: A weekly view of a subject’s mobility pattern
For an even more intuitive view of the GPS data, take a look at the video here:
VIDEO
From these GPS traces, we extracted daily mobility features, such as max home distance , circadian routine , probability of pauses , as described in Barnett et al., Biostatistics (2020) and defined mathematically in its supplementary material .
To get a flavor of what these features look like, the first six days of GPS data for a subject are attached below.
gps_df = read.table(gps_df_path,header = T, dec = ",", )[,c(1,2,97:111)]
#Define the column types
gps_df$Date = as.Date(gps_df$Date)
gps_df[,3:dim(gps_df)[2]] = apply(gps_df[,3:dim(gps_df)[2]], 2, function(x) as.numeric(x))
head(gps_df)
NA
3. Exclude Data
The first step before further analysis is to exclude data points (days) that had excessive amount of data missing. Here is a historgram of the minutes missing for all 3317 total days.
p=minMiss_histplot(gps_df,200, "All Data")
ggplotly(p)
Figure 2: Minutes missing for all collected days. There are 3317 total days. Dashlines indicate the corresponding percentile.
3a. remove first and last days
First, we will remove the first and last days from each subject, because the application was installed during mid-day at the beginning of the study and uninstalled mid-day at the end of the study.
# loop through each subj to remove 1st and last days of gps data
gps_df_clean = data.frame() #initiate a df
for (subj in unique(gps_df$IID)){ #loop through each subj
gps_df_subj <- subset(gps_df, IID == subj) #get gps_df per subject
gps_df_subj <- gps_df_subj[2:(dim(gps_df_subj)[1]-1),] #remove the 1st and last days
gps_df_clean <- rbind(gps_df_clean,gps_df_subj) #combine all subjs
}
This step removed 82 days. Now, dataset has 41 subjects, consisting of 3235 total days, and 15 GPS features.
Figure 3: Minutes missing after removing the first and last days of each subject. There are 3235 total days. Dashlines indicate the corresponding percentile.
3b. remove days at the sensitivity threshold
Next, we are removing the days with excessive data missingness. This is of course an arbitrary step. Therefore we will conduct a sensitivity analysis of the missingness threshold we set by performing the analysis across multiple different thresholds. For now, the current sensitivity cutoff is set at 1440, which amounts to only excluding those with 100% of GPS missing.
sensitivity_cutoff = 1440 # this controls the cutoff threshold
gps_df_clean2 = subset(gps_df_clean, MinsMissing < sensitivity_cutoff)
This step removed 79 days. Now, dataset has 41 subjects, consisting of 3156 total days, and 16 GPS features.
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
Figure 4: Minutes missing after removing days with all data missing. There are 3156 total days. Dashlines indicate the corresponding percentile.
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
Figure 5: A Zoom-in plot of minutes missing distribution after removing days with all data missing. There are 3156 total days. Dashlines indicate the corresponding percentile.
subj_minmissing = gps_df_clean2 %>% group_by(IID) %>% summarise(mean = mean(MinsMissing), n = n())
subj_minmissing_plot = ggplot(subj_minmissing) +
geom_point(aes(x = reorder(IID, mean), y = (1440-mean)/(1440-1296))) +
theme_cowplot() +
labs(title = "Data Completeness by Subject",part_times,"Data Partitions",
x = "Subjects", y = "Data Completeness") + scale_y_continuous(labels = scales::percent, limits=c(0,1)) +
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 8), plot.title = element_text(hjust = 0.5))
ggplotly(subj_minmissing_plot)
4. Random Data Partitions
To operationalize individual footprinting prediction, we randomly split each individual’s available GPS data to two half partitions for 100 times. We used the features from the first half to determine if we can identify the same individual using their data in the second unseen half. Again, to avoid any (un)lucky random splits, we repeated data partition 100 times.
set.seed(510)
subj_seq = list()
part_times = 100
for (subj in unique(gps_df_clean2$IID)){
subj_data = subset(gps_df_clean2, IID==subj)
subj_seq[[subj]] <-createDataPartition(subj_data$IID,times = part_times, p =0.5)
}
5. Build Correlation Matrix
Here, similar to Finn et al. Nat Neuro (2015) and Kaufmann et al. Nat Neuro (2015) , we built a Pearson correlation matrix among 16 available GPS features. Below is an illustrative sample of the variables and their correlations.
# an example of subj 1, and first half
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
example_data = gps_df_clean2[subj_seq$`14w5qlo8`$Resample001,3:17]
gps_cor = rquery.cormat(example_data, type = "full")
Figure 6: Correlation matrix of GPS features for a subject.
We then calculated the feature matrix for all 41 subjects in the dataset, seperately for each half data partition, and for each random split.
make_feature_matrix = function(gps_df) {
subj_mat_1 = list()
subj_mat_2 = list()
for (subj in unique(gps_df$IID)){
subj_data = subset(gps_df_clean2, IID==subj)
subj_mat_1[[subj]] = lapply(subj_seq[[subj]], function(list) rquery.cormat(subj_data[list,3:17], type = "flatten", graph = F)$r)
subj_mat_2[[subj]] = lapply(subj_seq[[subj]], function(list) rquery.cormat(subj_data[-list,3:17], type = "flatten", graph = F)$r)
}
return(list(subj_mat_1 = subj_mat_1, subj_mat_2 = subj_mat_2 ))
}
gps_clean2_feature = make_feature_matrix(gps_df_clean2)
gps_wide_matrix = data.frame()
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
for (subj in arrange(subj_days,`Days Collected`)$IID){
#if ((subj %in% subj_days$IID[which(subj_days$`Days Collected`>85 & subj_days$`Days Collected`<100)]) == T) {
for (part in 1:5){
gps_wide_matrix = rbind(gps_wide_matrix,gps_clean2_feature$subj_mat_1[[subj]][[part]]$cor)
gps_wide_matrix = rbind(gps_wide_matrix,gps_clean2_feature$subj_mat_2[[subj]][[part]]$cor)
}
#}
}
gps_wide_matrix = t(gps_wide_matrix)
big_corplot = rquery.cormat(gps_wide_matrix, type = "full",graph=FALSE)
levelplot(big_corplot$r,scales=list(draw=FALSE),col.regions = rev(rainbow(1000))[-c(1:20)], region =T, ylab.right = "Pearson correlation", main=list(label='GPS Feature Similarity'),xlab="",ylab="")
6. Match Target
Next we tried to match each target, defined by one subject’s feature matrix in the first half, to a feature matrix in the second half in the same random split. If the same individual’s feature matrix in the second half had the highest correlation among all subjects, then we assigned the match result 1, indicating a succussful match, otherwise 0, indicating an unsccussful match.
# creating a "database" against which target is to be matched with
subj_mat_1 = gps_clean2_feature$subj_mat_1
subj_mat_2 = gps_clean2_feature$subj_mat_2
calc_match_cor = function(subj_mat_1,subj_mat_2) {
database = list()
for (time in 1:part_times) {
database[[time]] = lapply(subj_mat_2, function(subjmat) subjmat[[time]]$cor)
}
# match target to database
match_cor = list()
for (subj1 in names(subj_mat_1)){ #loop through each subj
# create a list of target across partitions
target_list = lapply(subj_mat_1[[subj1]], function(part) part$cor)
# create a match list
for (time in 1:part_times){
target_subj_time = target_list[[time]] #loop through each partition
for (subj2 in names(subj_mat_2)){ #loop everyone in 2nd half
data_subj_time = subj_mat_2[[subj2]][[time]]$cor
match_cor[[subj1]][[as.character(time)]][[subj2]] = cor(target_subj_time,data_subj_time,use = "na.or.complete")
}
}
}
return(match_cor)
}
match_cor = calc_match_cor(subj_mat_1,subj_mat_2)
By tallying up all the successful and unsuccessful matchs in each of the 100 random splits, we calculated a distribution of match accuracy.
calc_acc_time=function(match_cor,part_times) {
acc_time = array()
for (time in 1:part_times){
acc_time[time] = 0
for (subj in names(subj_mat_1)){
max_position = which.max(unlist(match_cor[[subj]][[as.character(time)]]))
predicted_subj = names(subj_mat_1)[max_position]
if (predicted_subj == subj) {
acc_time[time] = acc_time[time] + 1
}
}
}
acc_time = acc_time/length(names(subj_mat_1))
return(acc_time)
}
acc_time = calc_acc_time(match_cor,part_times)
Over the 100 random splits, the mean match accuracy was 55.02%, with a standard deviation of 5.9%, high of 70.73%, and low of 36.59%, (95%CI: 0.54-0.56).
p_time_cor = hist_chx(acc_time, bins = 15, title = paste("Correlated Features: \n Average Accuracy Across",part_times,"Data Partitions"), xaxis = "Prediction Accuracy", yaxis = "Count")
ggplotly(p_time_cor)
Figure 7: Match accuracy distributon across 100 data splits. Mean match accuracy was 0.55 (95%CI: 0.54-0.56)
calc_acc_subj = function(match_cor, part_times){
acc_subj = array()
for (subj in names(subj_mat_1)){
acc_subj[subj] = 0
for (time in 1:part_times){
max_position = which.max(unlist(match_cor[[subj]][[as.character(time)]]))
predicted_subj = names(subj_mat_1)[max_position]
if (predicted_subj == subj) {
acc_subj[subj] = acc_subj[subj] + 1
}
}
}
acc_subj = acc_subj/part_times
acc_subj = acc_subj[-1]
return(acc_subj)
}
acc_subj = calc_acc_subj(match_cor, part_times)
In addition to the match accuracy across subjects, we also calculated the match accuracy for each individual. Across the 41 subjects, the mean match accuracy was 55.02%, with a standard deviation of 30.19%, high of 98%, and low of 4%.
subj_scatter = function(gps_df, acc_subj_vector, method){
subj_df <- data.frame(x=unique(gps_df$IID))
subj_df$y = acc_subj_vector
subj_df = subj_df[order(subj_df$y),]
subj_acc_plot = ggplot(subj_df) +
geom_point(aes(x = reorder(x, y), y = y)) +
theme_cowplot() +
labs(title = paste(method,"Features \n Subject Level Accuracy Across",part_times,"Data Partitions"),
x = "Subjects", y = "Prediction Accuracy") +
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 8), plot.title = element_text(hjust = 0.5))
return(subj_acc_plot)
}
ggplotly(subj_scatter(gps_df_clean2,acc_subj, "Correlated"))
Figure 8: Match accuracy distributon across 41 subjects. Mean match accuracy was 55.02%, with a standard deviation of 30.19%, high of 98%, and low of 4%. This shows dramatic differences in individual footprinting distinctiveness .
7. Permutation Test
To assess the statistical significance of the results above, we conducted a non-parametric permutation test. To do this, we randomly scrambled pair-wise subject-to-day linkage. We repeated this process 1000 times, and followed the same procedure as above to obtain a null distribution of average match accuracy across sample, and for each individual.
gps_df_perm = gps_df_clean2
perm_time = 1000
perm_acc_time = list()
perm_acc_subj = list()
for (i in 1:perm_time) {
perm_part_times = 1
print(paste("processing ...", i,"..."))
gps_df_perm$IID = sample(gps_df_perm$IID)
perm_subj_seq = make_subj_seq(gps_df_perm,part_times = perm_part_times)
perm_gps = make_feature_matrix(gps_df_perm,perm_subj_seq)
perm_mat_1 = perm_gps$subj_mat_1
perm_mat_2 = perm_gps$subj_mat_2
perm_match_cor = calc_match_cor(perm_mat_1,perm_mat_2)
perm_acc_time[[i]] = calc_acc_time(perm_match_cor,part_times = perm_part_times)
perm_acc_subj[[i]] = calc_acc_subj(perm_match_cor,part_times = perm_part_times)
}
Over the 1000 permutations, the mean match accuracy was 2.31%, with a standard deviation of 2.31%, high of 400%, and low of 2.3146341%.(95%CI: 0.02-0.02)
perm_acc_time_all = unlist(perm_acc_time)
q_time_cor = hist_chx(perm_acc_time_all, bins = 8, title = paste("Average Accuracy Across",length(perm_acc_time_all),"Permutations"), xaxis = "Prediction Accuracy", yaxis = "Count")
ggplotly(q_time_cor)
Figure 9: Match accuracy distributon across 1000 permutations. Mean match accuracy in permutation was 0.02 (95%CI: 0.02-0.02). This null distribution does not overlapp with the distributon using real data (Fig.7 ) at all.
We also calculated null distribution of match accuracy for each subject. Over the 1000 permutations, the mean match accuracy was 2.31%, with a standard deviation of 0.74%, high of 3.7%, and low of 0.6%.
perm_subj = list()
for (subj in subj_acc_plot$data$x) {
perm_subj$val[[subj]] = sapply(perm_acc_subj, function(perm) perm[which(names(perm) == subj)])
perm_subj$hist[[subj]] = hist_chx(perm_subj[[subj]]$val, bins = 8, title = paste(subj,": Accuracy Across \n",perm_time,"Permutations"), xaxis = "Prediction Accuracy", yaxis = "Count")
perm_subj$acc[[subj]] = sum(perm_subj$val[[subj]])/perm_time
}
subj_df <- data.frame(x=unique(gps_df_clean2$IID))
subj_df$y = acc_subj
subj_df$y_perm = unlist(perm_subj$acc)
subj_df = subj_df[order(subj_df$y),]
p_subj_cor_perm = ggplot(subj_df, aes(x = reorder(x, y), y = value)) +
geom_point(aes(y = y, col = "subject data")) +
geom_point(aes(y = y_perm, col = "permutation")) +
theme_cowplot() +
labs(title = paste("Cor Features \n Subject Level Accuracy Across",part_times,"Data Partitions"),
x = "Subjects", y = "Prediction Accuracy") +
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 8), plot.title = element_text(hjust = 0.5))
ggplotly(p_subj_cor_perm)
Figure 8: Match accuracy null distributon across 41 subjects. Mean match accuracy in permutation was 2.31%, with a standard deviation of 0.74%, high of 3.7%, and low of 0.6%. While individuals exhibited marked differences in footprinting distinctivenss, the subject with the lowest prediction accuracy was still statistically significant against the permutation test (i.e. for subject bkq7uyp6: data: 4 % vs. permutation: 0.6%).
8. Mean Features
We calculated the mean of each GPS feature as a metric of feature variability. Similar to above, we calculated mean separately for each data partition and for each individual.
mean_gps = function(gps_df){
mean_list = sapply(3:17,function(i) mean(gps_df[,i],na.rm=T))
names(mean_list) = colnames(gps_df)[3:17]
return(mean_list)
}
make_feature_mean = function(gps_df, subj_seq) {
subj_mat_1 = list()
subj_mat_2 = list()
for (subj in unique(gps_df$IID)){
subj_data = subset(gps_df_clean2, IID==subj)
subj_mat_1[[subj]] = lapply(subj_seq[[subj]], function(list) mean_gps(subj_data[list,]))
subj_mat_2[[subj]] = lapply(subj_seq[[subj]], function(list) mean_gps(subj_data[-list,]))
}
return(list(subj_mat_1 = subj_mat_1, subj_mat_2 = subj_mat_2 ))
}
gps_clean2_mean_feat = make_feature_mean(gps_df_clean2, subj_seq)
Figure 9: Feature mean across all subjects . The histogram are for data in the first random half split.
9. Variability Features
We calculated the root mean square of successive differences or RMSSD of each GPS feature as a metric of feature variability. Similar to above, we calculated RMSSD separately for each data partition and for each individual.
rmssd_gps = function(gps_df){
rmssd_list = sapply(3:17,function(i) rmssd_id(gps_df[,i], gps_df$IID,long=F))
names(rmssd_list) = colnames(gps_df)[3:17]
return(rmssd_list)
}
make_feature_rmssd = function(gps_df, subj_seq) {
subj_mat_1 = list()
subj_mat_2 = list()
for (subj in unique(gps_df$IID)){
subj_data = subset(gps_df_clean2, IID==subj)
subj_mat_1[[subj]] = lapply(subj_seq[[subj]], function(list) rmssd_gps(subj_data[list,]))
subj_mat_2[[subj]] = lapply(subj_seq[[subj]], function(list) rmssd_gps(subj_data[-list,]))
}
return(list(subj_mat_1 = subj_mat_1, subj_mat_2 = subj_mat_2 ))
}
gps_clean2_rmssd_feat = make_feature_rmssd(gps_df_clean2, subj_seq)
Figure 10: Feature variability for all subjects . Variability is measured by root mean square of successive differences (RMSSD). The histograms are for data in the first random half split.
10. Predict with New Features
calc_match_vector = function(subj_mat_1,subj_mat_2,method) {
part_times = length(subj_mat_1[[1]])
database = list()
for (time in 1:part_times) {
database[[time]] = lapply(subj_mat_2, function(subjmat) subjmat[[time]])
}
# match target to database
match_cor = list()
for (subj1 in names(subj_mat_1)){ #loop through each subj
# create a list of target across partitions
target_list = lapply(subj_mat_1[[subj1]], function(part) part)
# create a match list
for (time in 1:part_times){
target_subj_time = target_list[[time]] #loop through each partition
for (subj2 in names(subj_mat_2)){ #loop everyone in 2nd half
data_subj_time = subj_mat_2[[subj2]][[time]]
if (method == "cor") {
match_cor[[subj1]][[as.character(time)]][[subj2]] = cor(target_subj_time,data_subj_time,use = "na.or.complete")
}
else if (method == "rmse") {
match_cor[[subj1]][[as.character(time)]][[subj2]] = rmse(target_subj_time,data_subj_time)
}
}
}
}
return(match_cor)
}
match_mean_rmse = calc_match_vector(gps_clean2_mean_feat$subj_mat_1,gps_clean2_mean_feat$subj_mat_2,"rmse")
acc_time_mean_rmse = calc_acc_time(match_mean_rmse,part_times,"min")
acc_subj_mean_rmse = calc_acc_subj(match_mean_rmse, part_times,"min")
match_mean_cor = calc_match_vector(gps_clean2_mean_feat$subj_mat_1,gps_clean2_mean_feat$subj_mat_2,"cor")
acc_time_mean_cor = calc_acc_time(match_mean_cor,part_times,"max")
acc_subj_mean_cor = calc_acc_subj(match_mean_cor, part_times,"max")
match_rmssd_rmse = calc_match_vector(gps_clean2_rmssd_feat$subj_mat_1,gps_clean2_rmssd_feat$subj_mat_2, "rmse")
acc_time_rmssd_rmse = calc_acc_time(match_rmssd_rmse,part_times, "min")
acc_subj_rmssd_rmse = calc_acc_subj(match_rmssd_rmse, part_times, "min")
match_rmssd_cor = calc_match_vector(gps_clean2_rmssd_feat$subj_mat_1,gps_clean2_rmssd_feat$subj_mat_2, "cor")
acc_time_rmssd_cor = calc_acc_time(match_rmssd_cor,part_times, "max")
acc_subj_rmssd_cor = calc_acc_subj(match_rmssd_cor, part_times, "max")
11. Permutation Tests with New Features
11a. Average Accuracy
perm_vector = function(gps_df, perm_time, method_feature, method_match){
gps_df_perm = gps_df
perm_acc_time = list()
perm_acc_subj = list()
for (i in 1:perm_time) {
perm_part_times = 1
print(paste("processing ...", i,"..."))
gps_df_perm$IID = sample(gps_df_perm$IID)
perm_subj_seq = make_subj_seq(gps_df_perm,part_times = perm_part_times)
if (method_feature == "rmssd") {
perm_gps = make_feature_rmssd(gps_df_perm,perm_subj_seq)
} else if (method_feature == "mean"){
perm_gps = make_feature_mean(gps_df_perm,perm_subj_seq)
}
perm_mat_1 = perm_gps$subj_mat_1
perm_mat_2 = perm_gps$subj_mat_2
if (method_match == "cor") {
perm_match = calc_match_vector(perm_mat_1,perm_mat_2, "cor")
perm_acc_time[[i]] = calc_acc_time(perm_match,part_times = perm_part_times, "max")
perm_acc_subj[[i]] = calc_acc_subj(perm_match,part_times = perm_part_times, "max")
}
else if (method_match == "rmse"){
perm_match = calc_match_vector(perm_mat_1,perm_mat_2, "rmse")
perm_acc_time[[i]] = calc_acc_time(perm_match,part_times = perm_part_times, "min")
perm_acc_subj[[i]] = calc_acc_subj(perm_match,part_times = perm_part_times, "min")
}
}
return(list(perm_acc_time= perm_acc_time,perm_acc_subj = perm_acc_subj))
}
#perm_mean = perm_vector(gps_df_clean2, 1000, "mean")
p_time_mean = hist_chx(acc_time_mean, bins = 8, title = paste("Mean Features: \n Average Accuracy Across",length(acc_time_mean),"Data Partitions"), xaxis = "Prediction Accuracy", yaxis = "Count")
q_time_mean = hist_chx(unlist(perm_mean$perm_acc_time), bins = 8, title = paste("Mean Features: \n Average Accuracy Across",length(perm_mean$perm_acc_time),"Permutations"), xaxis = "Prediction Accuracy", yaxis = "Count")
p_time_mean + q_time_mean
Figure 11: Prediction accuracy using mean features and matched by max pearson correlation .
Figure 11: Prediction accuracy using mean features and matched by min root mean squared .
#perm_rmssd = perm_vector(gps_df_clean2, 1000, "rmssd")
p_time_rmssd = hist_chx(acc_time_rmssd, bins = 8, title = paste("RMSSD Features: \n Average Accuracy Across",length(acc_time_rmssd),"Data Partitions"), xaxis = "Prediction Accuracy", yaxis = "Count")
q_time_rmssd = hist_chx(unlist(perm_rmssd$perm_acc_time), bins = 8, title = paste("RMSSD Features: Average Accuracy Across",length(perm_mean$perm_acc_time),"Permutations"), xaxis = "Prediction Accuracy", yaxis = "Count")
p_time_rmssd + q_time_rmssd
Figure 12: Prediction accuracy using RMSSD features and matched by max pearson correlation .
Figure 13: Prediction accuracy using RMSSD features and matched by min root mean squared .
combine_perm_subj = function(acc_subj_plot, perm_acc_subj) {
perm_subj = list()
for (subj in acc_subj_plot$data$x) {
perm_subj$val[[subj]] = sapply(perm_acc_subj, function(perm) perm[which(names(perm) == subj)])
perm_subj$hist[[subj]] = hist_chx(perm_subj[[subj]]$val, bins = 8, title = paste(subj,": Accuracy Across \n",perm_time,"Permutations"), xaxis = "Prediction Accuracy", yaxis = "Count")
perm_subj$acc[[subj]] = sum(perm_subj$val[[subj]])/perm_time
}
return(perm_subj)
}
subj_scatter_perm = function(gps_df,acc_subj,perm_subj, method){
subj_df <- data.frame(x=unique(gps_df$IID))
subj_df$y = acc_subj
subj_df$y_perm = unlist(perm_subj$acc)
subj_df = subj_df[order(subj_df$y),]
p = ggplot(subj_df, aes(x = reorder(x, y), y = value)) +
geom_point(aes(y = y, col = "subject data")) +
geom_point(aes(y = y_perm, col = "permutation")) +
theme_cowplot() +
labs(title = paste(method,"Features \n Subject Level Accuracy Across",part_times,"Data Partitions"),
x = "Subjects", y = "Prediction Accuracy") +
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 8), plot.title = element_text(hjust = 0.5))
return(p)
}
p_subj_mean = subj_scatter(gps_df_clean2, acc_subj_mean, "Mean")
perm_subj_mean = combine_perm_subj(p_subj_mean,perm_mean$perm_acc_subj)
p_subj_mean_perm = subj_scatter_perm(gps_df_clean2, acc_subj_mean, perm_subj_mean, "Mean")
ggplotly(p_subj_mean_perm)
Figure 14: Subject level prediction accuracy using mean features and matched by max pearson correlation .
p_subj_rmssd = subj_scatter(gps_df_clean2, acc_subj_rmssd, "RMSSD")
perm_subj_rmssd = combine_perm_subj(p_subj_rmssd,perm_rmssd$perm_acc_subj)
p_subj_rmssd_perm = subj_scatter_perm(gps_df_clean2, acc_subj_rmssd, perm_subj_rmssd, "RMSSD")
ggplotly(p_subj_rmssd_perm)
Figure 15: Subject level prediction accuracy using RMSSD features and matched by max pearson correlation .
12. Compare Features
p_time_cor + p_time_mean + p_time_rmssd
Figure 16: Prediction accuracy across three feature sets .
p_subj_cor_perm / p_subj_mean_perm / p_subj_rmssd_perm
Figure 17: Subject level prediction accuracy across three feature sets .
13. Confounding variables
Figure 18: Relationships between subject level prediction accuracy and individual features . Notably, there was no relationship between individual data missingness and subject level prediction accuracy.
Figure 19: Relationships between subject level prediction accuracy and data quantity .
14. Combine Features
gps_clean2_combined_feat = list()
for (subj in names(subj_mat_1)){
for (time in 1:part_times){
cor_feat1 = gps_clean2_feature$subj_mat_1[[subj]][[time]]$cor
mean_feat1 = gps_clean2_mean_feat$subj_mat_1[[subj]][[time]]
rmssd_feat1 = gps_clean2_rmssd_feat$subj_mat_1[[subj]][[time]]
cor_feat2 = gps_clean2_feature$subj_mat_2[[subj]][[time]]$cor
mean_feat2 = gps_clean2_mean_feat$subj_mat_2[[subj]][[time]]
rmssd_feat2 = gps_clean2_rmssd_feat$subj_mat_2[[subj]][[time]]
gps_clean2_combined_feat$subj_mat_1[[subj]][[time]] = c(cor_feat1, mean_feat1, rmssd_feat1)
gps_clean2_combined_feat$subj_mat_2[[subj]][[time]] = c(cor_feat2, mean_feat2, rmssd_feat2)
}
}
match_combined_feat = calc_match_vector(gps_clean2_combined_feat$subj_mat_1, gps_clean2_combined_feat$subj_mat_2)
acc_time_cb = calc_acc_time(match_combined_feat, part_times)
acc_subj_cb = calc_acc_subj(match_combined_feat, part_times)
p = hist_chx(acc_time_cb, bins = 8, title = paste("Combined Features \n by data partition"), xaxis = "Prediction Accuracy", yaxis = "Count")
q = hist_chx(acc_subj_cb, bins = 8, title = paste("Combined Features \n by subject"), xaxis = "Prediction Accuracy", yaxis = "Count")
p + q
Figure 20: Prediction accuracy when all three sets of features were combined .
15. Associations with Psychopathology
ids = read.xlsx(file.path(project_path,"data/subjecttracker_4.xlsx"))[1:length(unique(gps_df_clean2$IID)),]
psych_score = read.csv(file.path(project_path,"data/self_report_scored_20200128.csv"))
# psych_pro = psych %>% filter(ari_proband_complete == 2)
psych_beiwe = inner_join(ids,psych_score, by = c("BBLID" = "bblid"))
acc_subj_df = data.frame(beiweID = names(acc_subj), acc = acc_subj)
psych_beiwe_acc_subj = inner_join(acc_subj_df,psych_beiwe, by = "beiweID")
psych_item = read.csv(file.path(project_path,"data/self_report_itemwise.csv"))
psych_sum = psych_beiwe_acc_subj %>%
mutate(sum_als = rowSums(.[grep("^als_[0-9]$",colnames(psych_beiwe_acc_subj))], na.rm = T)) %>%
mutate(sum_ari = rowSums(.[grep("^ari_[0-9]$",colnames(psych_beiwe_acc_subj))], na.rm = T))
psych_sp = function(psych_df,psych_var, xlab) {
sp = ggscatter(psych_df, y = psych_var, x = "acc",
add = "reg.line", # Add regressin line
add.params = list(color = "black", fill = "lightgray"), # Customize reg. line
conf.int = TRUE # Add confidence interval
) + stat_cor(method = "pearson") +
theme_cowplot() + theme(plot.title = element_text(hjust = 0.5)) +
labs(x = "Prediction Accuracy", y = xlab, title = xlab)
return(sp)
}
sp_als = psych_sp(psych_beiwe_acc_subj, 'als_score_avg',"Affective Lability Score (ALS)")
sp_ari = psych_sp(psych_beiwe_acc_subj, 'ari_score_total',"Affective Reactivity Index (ARI)")
sp_als + sp_ari
Figure 21: Associations between irritability and prediction accuracy .
16. Associations with Cognition
ids = read.xlsx(file.path(project_path,"data/subjecttracker_4.xlsx"))[1:length(unique(gps_df_clean2$IID)),]
psych_score = read.csv(file.path(project_path,"data/self_report_scored_20200128.csv"))
# psych_pro = psych %>% filter(ari_proband_complete == 2)
psych_beiwe = inner_join(ids,psych_score, by = c("BBLID" = "bblid"))
acc_subj_df = data.frame(beiweID = names(acc_subj), acc = acc_subj)
psych_beiwe_acc_subj = inner_join(acc_subj_df,psych_beiwe, by = "beiweID")
psych_item = read.csv(file.path(project_path,"data/self_report_itemwise.csv"))
psych_sum = psych_beiwe_acc_subj %>%
mutate(sum_als = rowSums(.[grep("^als_[0-9]$",colnames(psych_beiwe_acc_subj))], na.rm = T)) %>%
mutate(sum_ari = rowSums(.[grep("^ari_[0-9]$",colnames(psych_beiwe_acc_subj))], na.rm = T))
psych_sp = function(psych_df,psych_var, xlab) {
sp = ggscatter(psych_df, y = psych_var, x = "acc",
add = "reg.line", # Add regressin line
add.params = list(color = "black", fill = "lightgray"), # Customize reg. line
conf.int = TRUE # Add confidence interval
) + stat_cor(method = "pearson") +
theme_cowplot() + theme(plot.title = element_text(hjust = 0.5)) +
labs(x = "Prediction Accuracy", y = xlab, title = xlab)
return(sp)
}
sp_als = psych_sp(psych_beiwe_acc_subj, 'als_score_avg',"Affective Lability Score (ALS)")
sp_ari = psych_sp(psych_beiwe_acc_subj, 'ari_score_total',"Affective Reactivity Index (ARI)")
sp_als + sp_ari
17. Misc Info
#sessionInfo()
LS0tCnRpdGxlOiAiR1BTIEZvb3RwcmludGluZyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBpbmNsdWRlczoKICAgICAgYWZ0ZXJfYm9keTogZm9vdGVyLmh0bWwKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6CiAgICAgIHRvY19jb2xsYXBzZWQ6IHllcwotLS0KYHIgU3lzLnRpbWUoKWAKCkF1dGhvcjogW0NlZHJpYyBIdWNodWFuIFhpYV0oaHR0cHM6Ly93d3cucGVubmxpbmMuaW8vdGVhbS9DZWRyaWMtSHVjaHVhbi1YaWEpIChbZW1haWxdKGh4aWFAdXBlbm4uZWR1KSwgW2dpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL2NlZHJpY3gvKSkKCkFmZmlsaWF0aW9uOiBQZW5uIExpZmVzcGFuIEluZm9ybWF0aWNzIGFuZCBOZXVyb2ltYWdpbmcgQ2VudGVyIChbUGVubkxJTkNdKHBlbm5saW5jLmlvKSkgCgoqKioKCgpUaGlzICpHUFMgRm9vdHByaW50aW5nIHByb2plY3QqIGlzIGluc3BpcmVkIGJ5IFtGaW5uIGV0IGFsLiBOYXQgTmV1cm8gICgyMDE1KV0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9ubi40MTM1KSBhbmQgW0thdWZtYW5uIGV0IGFsLiBOYXQgTmV1cm8gKDIwMTUpXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5My0wMTktMDQ3MS03KS4gVGhlc2UgYXV0aG9ycyBzdHVkaWVkIGhvdyBwZXJzb25hbCBkaWZmZXJlbmNlcyBpbiBmdW5jdGlvbmFsIGJyYWluIGNvbm5lY3Rpdml0eSBjYW4gaWRlbnRpZnkgaW5kaXZpZHVhbHMsIG1hdHVyZSBkdXJpbmcgZGV2ZWxvcG1lbnQsIGFsdGVyIGluIG5ldXJvcHN5Y2hpYXRyaWMgaWxsbmVzcywgYW5kIGRpZmZlciBiZXR3ZWVuIGdlbmRlcnMuIFRoZSBhdXRob3JzIHJlZmVycmVkIHRvIHRoZXNlIGluZGl2aWR1YWwgZGlmZmVyZW5jZXMgYXMgKmJyYWluIGZpbmdlcnByaW50aW5nKi4KCj4gSGVyZSwgd2UgYXBwbHkgdGhlIGZpbmdlcnByaW50aW5nIHRlY2huaXF1ZSB0byBoaWdobHkgc2FtcGxlZCBHUFMgZGF0YSBpbiBhIGNsaW5pY2FsIHNhbXBsZSBvZiB5b3V0aC4gV2UgYXJlIGludGVyZXN0ZWQgaW4gaG93IHRoZWlyIGluZGl2aWR1YWwgbW9iaWxpdHkgcGF0dGVybnMgY2FuIGRpc3Rpbmd1aXNoIG9uZSBhbm90aGVyJ3MgaWRlbnRpdHksIGRpZmZlciBiZXR3ZWVuIGdlbmRlcnMsIGFuZCBhbHRlciBpbiBwc3ljaG9wYXRob2xvZ2ljYWwgZ3JvdXBzLiBJbiBvdGhlciB3b3JkcywgY2FuIG91ciBmb290cHJpbnQgdGVsbCB1cyBhcGFydCBhbmQgc29tZXRoaW5nIGFib3V0IG91ciBiZWhhdmlvcj8gCgpHUFMgZGF0YSBwcmVwcm9jZXNzaW5nIHdhcyBwZXJmb3JtZWQgYWNjb3JkaW5nIHRvIFtJYW4gQmFybmV0dCdzIGltcHV0YXRpb24gYWxnb3JpdGhtXShodHRwczovL2dpdGh1Yi5jb20vaWFuamFtZXNiYXJuZXR0L1NtYXJ0cGhvbmVTZW5zb3JQaXBlbGluZSksIG9yaWdpbmFsbHkgcHVibGlzaGVkIGluIFtCaW9zdGF0aXN0aWNzICgyMDIwKV0oaHR0cHM6Ly9hY2FkZW1pYy5vdXAuY29tL2Jpb3N0YXRpc3RpY3MvYXJ0aWNsZS1hYnN0cmFjdC8yMS8yL2U5OC81MTQ1OTA4KS4KCiMjIyAxLiBTZXR1cCBFbnZpcm9ubWVudCAKYGBge3IgbG9hZCBsaWJyYXJpZXMsIG1lc3NhZ2U9RkFMU0V9CnJlcXVpcmUoZ2dwbG90MikKcmVxdWlyZShzdW1tYXJ5dG9vbHMpCnJlcXVpcmUoY293cGxvdCkKcmVxdWlyZShjYXJldCkKcmVxdWlyZShjb3JycGxvdCkKcmVxdWlyZShSQ29sb3JCcmV3ZXIpCnJlcXVpcmUodmVtYmVkcikKcmVxdWlyZShSbWlzYykKcmVxdWlyZSh2YXJpYW4pCnJlcXVpcmUocGF0Y2h3b3JrKQpyZXF1aXJlKHBsb3RseSkKcmVxdWlyZShNZXRyaWNzKQpyZXF1aXJlKGRwbHlyKQpyZXF1aXJlKGdncHVicikKcmVxdWlyZShtb3NhaWMpCnJlcXVpcmUob3Blbnhsc3gpCnNvdXJjZSgnfi9Eb2N1bWVudHMvR2l0SHViL1NtYXJ0cGhvbmVTZW5zb3JQaXBlbGluZS9FeHRyYS9wbG90dGluZ19mdW5jdGlvbnMuUicpCmBgYAoKCmBgYHtyIGRlZmluZSBwYXRoc30KcHJvamVjdF9wYXRoID0gIn4vRG9jdW1lbnRzL3hpYV9ncHMvIgpkYXRhX3BhdGggPSBmaWxlLnBhdGgocHJvamVjdF9wYXRoLCJiZWl3ZV9vdXRwdXRfMDQzMDIwIikKZ3BzX2RmX3BhdGggPSBmaWxlLnBhdGgoZGF0YV9wYXRoLCJQcm9jZXNzZWRfRGF0YS9Hcm91cC9mZWF0dXJlX21hdHJpeC50eHQiKQpgYGAKCiMjIyAyLiBHUFMgRmVhdHVyZXMKVGhlIGN1cnJlbnQgZGF0YSBoYXMgYHIgbGVuZ3RoKHVuaXF1ZShncHNfZGYkSUlEKSlgIHN1YmplY3RzLCBjb25zaXN0aW5nIG9mIGByIGRpbShncHNfZGYpWzFdYCB0b3RhbCBkYXlzLCBhbmQgYHIgbGVuZ3RoKGNvbG5hbWVzKGdwc19kZikpLTJgIEdQUyBmZWF0dXJlcy4gVG8gY29uc2VydmUgYmF0dGVyeSBsaWZlLCBhIHN1YmplY3QncyBHUFMgY29vcmRpbmF0ZXMgd2VyZSB0cmFja2VkIGZvciBpbiBhIDItbWluLW9uIGFuZCAxOC1taW4tb2ZmIGN5Y2xlIGV2ZXJ5ZGF5IHVzaW5nIHRoZWlyIG93biBtb2JpbGUgZGV2aWNlIHZpYSB0aGUgW0JlaXdlIHBsYXRmb3JtXShodHRwczovL3d3dy5iZWl3ZS5vcmcpLiBXZSB1c2VkIFtJYW4gQmFybmV0dCdzIGFsZ29yaXRobV0oaHR0cHM6Ly9naXRodWIuY29tL2lhbmphbWVzYmFybmV0dC9TbWFydHBob25lU2Vuc29yUGlwZWxpbmUpIHRvIGltcHV0ZSB0aGUgbWlzc2luZyBkYXRhIGR1cmluZyB0aGUgb2ZmIGN5Y2xlcy4gQXNzdW1pbmcgbm8gbW9yZSBkYXRhIHdhcyBtaXNzaW5nIGR1ZSB0byB2YXJpb3VzIGZhY3RvcnMsIG9uZSB3b3VsZCBnZW5lcmF0ZSBgciAyKjMqMjRgIG1pbnMgb2YgR1BTIGRhdGEgcGVyIGRheSwgb3IgYHIgMi8yMCoxMDBgJSBvZiB0b3RhbCBtaW51dGVzIGluIGEgZGF5LiAKCkFzIHlvdSBjYW4gc2VlIGZyb20gdGhlIGZpZ3VyZSBiZWxvdywgd2UgY2FuIG5pY2VseSByZWNvbnN0cnVjdCBhbiBpbmRpdmlkdWFscycgbW9iaWxpdHkgdHJhamVjdG9yeSBmcm9tIHRoZXNlIGRhdGEuCjxjZW50ZXI+CiFbZ3BzX3RyYWNrXSguL2dwc190cmFja19zYW1wbGUucG5nKQoqKkZpZ3VyZSAxOiBBIHdlZWtseSB2aWV3IG9mIGEgc3ViamVjdCdzIG1vYmlsaXR5IHBhdHRlcm4qKgo8L2NlbnRlcj4KCkZvciBhbiBldmVuIG1vcmUgaW50dWl0aXZlIHZpZXcgb2YgdGhlIEdQUyBkYXRhLCB0YWtlIGEgbG9vayBhdCB0aGUgdmlkZW8gaGVyZToKCjxjZW50ZXI+CmBgYHtyIGVjaG89RkFMU0V9CmVtYmVkX3VybCgiaHR0cHM6Ly95b3V0dS5iZS9Lb2JFU2d0Zm9PbyIpCmBgYAo8L2NlbnRlcj4KCgogPGJyPjxicj48YnI+PGJyPgoKRnJvbSB0aGVzZSBHUFMgdHJhY2VzLCB3ZSBleHRyYWN0ZWQgZGFpbHkgbW9iaWxpdHkgZmVhdHVyZXMsIHN1Y2ggYXMgKm1heCBob21lIGRpc3RhbmNlKiwgKmNpcmNhZGlhbiByb3V0aW5lKiwgKnByb2JhYmlsaXR5IG9mIHBhdXNlcyosIGFzIGRlc2NyaWJlZCBpbiBbQmFybmV0dCBldCBhbC4sIEJpb3N0YXRpc3RpY3MgKDIwMjApXShodHRwczovL2FjYWRlbWljLm91cC5jb20vYmlvc3RhdGlzdGljcy9hcnRpY2xlLWFic3RyYWN0LzIxLzIvZTk4LzUxNDU5MDgpIGFuZCBkZWZpbmVkIG1hdGhlbWF0aWNhbGx5IGluIGl0cyBbc3VwcGxlbWVudGFyeSBtYXRlcmlhbF0oaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL29wZW4/aWQ9MWpIRkpMWGpVU3dvc2VyTjV0UHRBLS1oOUdFY1ZmWjEwKS4KClRvIGdldCBhIGZsYXZvciBvZiB3aGF0IHRoZXNlIGZlYXR1cmVzIGxvb2sgbGlrZSwgdGhlIGZpcnN0IHNpeCBkYXlzIG9mIEdQUyBkYXRhIGZvciBhIHN1YmplY3QgYXJlIGF0dGFjaGVkIGJlbG93LiAKYGBge3IgcmVhZF9ncHN9Cmdwc19kZiA9IHJlYWQudGFibGUoZ3BzX2RmX3BhdGgsaGVhZGVyID0gVCwgZGVjID0gIiwiLCApWyxjKDEsMiw5NzoxMTEpXQoKI0RlZmluZSB0aGUgY29sdW1uIHR5cGVzCmdwc19kZiREYXRlID0gYXMuRGF0ZShncHNfZGYkRGF0ZSkKZ3BzX2RmWywzOmRpbShncHNfZGYpWzJdXSA9IGFwcGx5KGdwc19kZlssMzpkaW0oZ3BzX2RmKVsyXV0sIDIsIGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMoeCkpCmhlYWQoZ3BzX2RmKQoKYGBgCgojIyMgMy4gRXhjbHVkZSBEYXRhClRoZSBmaXJzdCBzdGVwIGJlZm9yZSBmdXJ0aGVyIGFuYWx5c2lzIGlzIHRvIGV4Y2x1ZGUgZGF0YSBwb2ludHMgKGRheXMpIHRoYXQgaGFkIGV4Y2Vzc2l2ZSBhbW91bnQgb2YgZGF0YSBtaXNzaW5nLiBIZXJlIGlzIGEgaGlzdG9yZ3JhbSBvZiB0aGUgbWludXRlcyBtaXNzaW5nIGZvciBhbGwgYHIgZGltKGdwc19kZilbMV1gIHRvdGFsIGRheXMuIAoKYGBge3IgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcD1taW5NaXNzX2hpc3RwbG90KGdwc19kZiwyMDAsICJBbGwgRGF0YSIpCmdncGxvdGx5KHApCmBgYAoqKkZpZ3VyZSAyOiBNaW51dGVzIG1pc3NpbmcgZm9yIGFsbCBjb2xsZWN0ZWQgZGF5cy4qKiBUaGVyZSBhcmUgYHIgZGltKGdwc19kZilbMV1gIHRvdGFsIGRheXMuIERhc2hsaW5lcyBpbmRpY2F0ZSB0aGUgY29ycmVzcG9uZGluZyBwZXJjZW50aWxlLgoKCgoKIyMjIyAzYS4gIHJlbW92ZSBmaXJzdCBhbmQgbGFzdCBkYXlzCkZpcnN0LCB3ZSB3aWxsIHJlbW92ZSB0aGUgZmlyc3QgYW5kIGxhc3QgZGF5cyBmcm9tIGVhY2ggc3ViamVjdCwgYmVjYXVzZSB0aGUgYXBwbGljYXRpb24gd2FzIGluc3RhbGxlZCBkdXJpbmcgbWlkLWRheSBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSBzdHVkeSBhbmQgdW5pbnN0YWxsZWQgbWlkLWRheSBhdCB0aGUgZW5kIG9mIHRoZSBzdHVkeS4gCgpgYGB7ciAxc3RfbGFzdF9kYXlzfQojIGxvb3AgdGhyb3VnaCBlYWNoIHN1YmogdG8gcmVtb3ZlIDFzdCBhbmQgbGFzdCBkYXlzIG9mIGdwcyBkYXRhCmdwc19kZl9jbGVhbiA9IGRhdGEuZnJhbWUoKSAjaW5pdGlhdGUgYSBkZgpmb3IgKHN1YmogaW4gdW5pcXVlKGdwc19kZiRJSUQpKXsgI2xvb3AgdGhyb3VnaCBlYWNoIHN1YmoKICBncHNfZGZfc3ViaiA8LSBzdWJzZXQoZ3BzX2RmLCBJSUQgPT0gc3ViaikgI2dldCBncHNfZGYgcGVyIHN1YmplY3QKICBncHNfZGZfc3ViaiA8LSBncHNfZGZfc3VialsyOihkaW0oZ3BzX2RmX3N1YmopWzFdLTEpLF0gI3JlbW92ZSB0aGUgMXN0IGFuZCBsYXN0IGRheXMKICBncHNfZGZfY2xlYW4gPC0gcmJpbmQoZ3BzX2RmX2NsZWFuLGdwc19kZl9zdWJqKSAjY29tYmluZSBhbGwgc3VianMKfQpgYGAKClRoaXMgc3RlcCByZW1vdmVkIGByIGRpbShncHNfZGYpWzFdIC0gZGltKGdwc19kZl9jbGVhbilbMV1gIGRheXMuIE5vdywgZGF0YXNldCBoYXMgYHIgbGVuZ3RoKHVuaXF1ZShncHNfZGZfY2xlYW4kSUlEKSlgIHN1YmplY3RzLCBjb25zaXN0aW5nIG9mIGByIGRpbShncHNfZGZfY2xlYW4pWzFdYCB0b3RhbCBkYXlzLCBhbmQgYHIgbGVuZ3RoKGNvbG5hbWVzKGdwc19kZl9jbGVhbikpLTJgIEdQUyBmZWF0dXJlcy4KCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD00LCBlY2hvPUZBTFNFfQpwPW1pbk1pc3NfaGlzdHBsb3QoZ3BzX2RmX2NsZWFuLDIwMCwgIkFmdGVyIFJlbW92aW5nIDFzdCBhbmQgTGFzdCBEYXlzIikKZ2dwbG90bHkocCkKYGBgCioqRmlndXJlIDM6IE1pbnV0ZXMgbWlzc2luZyBhZnRlciByZW1vdmluZyB0aGUgZmlyc3QgYW5kIGxhc3QgZGF5cyBvZiBlYWNoIHN1YmplY3QuKiogVGhlcmUgYXJlIGByIGRpbShncHNfZGZfY2xlYW4pWzFdYCB0b3RhbCBkYXlzLiBEYXNobGluZXMgaW5kaWNhdGUgdGhlIGNvcnJlc3BvbmRpbmcgcGVyY2VudGlsZS4KCiMjIyMgM2IuICByZW1vdmUgZGF5cyBhdCB0aGUgc2Vuc2l0aXZpdHkgdGhyZXNob2xkCk5leHQsIHdlIGFyZSByZW1vdmluZyB0aGUgZGF5cyB3aXRoIGV4Y2Vzc2l2ZSBkYXRhIG1pc3NpbmduZXNzLiBUaGlzIGlzIG9mIGNvdXJzZSBhbiBhcmJpdHJhcnkgc3RlcC4gVGhlcmVmb3JlIHdlIHdpbGwgY29uZHVjdCBhIHNlbnNpdGl2aXR5IGFuYWx5c2lzIG9mIHRoZSBtaXNzaW5nbmVzcyB0aHJlc2hvbGQgd2Ugc2V0IGJ5IHBlcmZvcm1pbmcgdGhlIGFuYWx5c2lzIGFjcm9zcyBtdWx0aXBsZSBkaWZmZXJlbnQgdGhyZXNob2xkcy4gRm9yIG5vdywgdGhlIGN1cnJlbnQgc2Vuc2l0aXZpdHkgY3V0b2ZmIGlzIHNldCBhdCBgciBzZW5zaXRpdml0eV9jdXRvZmZgLCB3aGljaCBhbW91bnRzIHRvIG9ubHkgZXhjbHVkaW5nIHRob3NlIHdpdGggYHIgc2Vuc2l0aXZpdHlfY3V0b2ZmLzE0NDAqMTAwYCUgb2YgR1BTIG1pc3NpbmcuCgpgYGB7ciByZXN1bHRzPSdhc2lzJ30Kc2Vuc2l0aXZpdHlfY3V0b2ZmID0gMTQ0MCAjIHRoaXMgY29udHJvbHMgdGhlIGN1dG9mZiB0aHJlc2hvbGQKZ3BzX2RmX2NsZWFuMiA9IHN1YnNldChncHNfZGZfY2xlYW4sIE1pbnNNaXNzaW5nIDwgc2Vuc2l0aXZpdHlfY3V0b2ZmKQpgYGAKClRoaXMgc3RlcCByZW1vdmVkIGByIGRpbShncHNfZGZfY2xlYW4pWzFdIC0gZGltKGdwc19kZl9jbGVhbjIpWzFdYCBkYXlzLiBOb3csIGRhdGFzZXQgaGFzIGByIGxlbmd0aCh1bmlxdWUoZ3BzX2RmX2NsZWFuMiRJSUQpKWAgc3ViamVjdHMsIGNvbnNpc3Rpbmcgb2YgYHIgZGltKGdwc19kZl9jbGVhbjIpWzFdYCB0b3RhbCBkYXlzLCBhbmQgYHIgbGVuZ3RoKGNvbG5hbWVzKGdwc19kZl9jbGVhbjIpKS0yYCBHUFMgZmVhdHVyZXMuCgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NCwgZWNobz1GQUxTRX0KcCA9IG1pbk1pc3NfaGlzdHBsb3QoZ3BzX2RmX2NsZWFuMiwyMDAsIHBhc3RlKCJBZnRlciBSZW1vdmluZyBNaXNzaW5nIEdyZWF0ZXIgdGhhbiAiLHNlbnNpdGl2aXR5X2N1dG9mZikpCmdncGxvdGx5KHApCmBgYAoqKkZpZ3VyZSA0OiBNaW51dGVzIG1pc3NpbmcgYWZ0ZXIgcmVtb3ZpbmcgZGF5cyB3aXRoIGFsbCBkYXRhIG1pc3NpbmcuKiogVGhlcmUgYXJlIGByIGRpbShncHNfZGZfY2xlYW4yKVsxXWAgdG90YWwgZGF5cy4gRGFzaGxpbmVzIGluZGljYXRlIHRoZSBjb3JyZXNwb25kaW5nIHBlcmNlbnRpbGUuCgoKYGBge3IgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTQsIGVjaG89RkFMU0V9CnAgPSBtaW5NaXNzX2hpc3RwbG90KHN1YnNldChncHNfZGZfY2xlYW4yLE1pbnNNaXNzaW5nPj0xMjk2KSwyMDAsICJab29tIEluIFBsb3QiKQpnZ3Bsb3RseShwKQpgYGAKKipGaWd1cmUgNTogQSBab29tLWluIHBsb3Qgb2YgbWludXRlcyBtaXNzaW5nIGRpc3RyaWJ1dGlvbiBhZnRlciByZW1vdmluZyBkYXlzIHdpdGggYWxsIGRhdGEgbWlzc2luZy4qKiBUaGVyZSBhcmUgYHIgZGltKGdwc19kZl9jbGVhbjIpWzFdYCB0b3RhbCBkYXlzLiBEYXNobGluZXMgaW5kaWNhdGUgdGhlIGNvcnJlc3BvbmRpbmcgcGVyY2VudGlsZS4KCmBgYHtyIGludmVzdGlnYXRlIGluZGl2aWR1YWwgZGF0YSBxdWFsaXR5LCBmaWcuYWxpZ249ImNlbnRlciIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnN1YmpfbWlubWlzc2luZyA9IGdwc19kZl9jbGVhbjIgJT4lIGdyb3VwX2J5KElJRCkgJT4lIHN1bW1hcmlzZShtZWFuID0gbWVhbihNaW5zTWlzc2luZyksIG4gPSBuKCkpCgpzdWJqX21pbm1pc3NpbmdfcGxvdCA9IGdncGxvdChzdWJqX21pbm1pc3NpbmcpICsgCiAgZ2VvbV9wb2ludChhZXMoeCA9IHJlb3JkZXIoSUlELCBtZWFuKSwgeSA9ICgxNDQwLW1lYW4pLygxNDQwLTEyOTYpKSkgKyAKICAgdGhlbWVfY293cGxvdCgpICsgCiAgbGFicyh0aXRsZSA9ICJEYXRhIENvbXBsZXRlbmVzcyBieSBTdWJqZWN0IixwYXJ0X3RpbWVzLCJEYXRhIFBhcnRpdGlvbnMiLCAKICB4ID0gIlN1YmplY3RzIiwgeSA9ICJEYXRhIENvbXBsZXRlbmVzcyIpICsgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwgbGltaXRzPWMoMCwxKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZSA9IDgpLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKZ2dwbG90bHkoc3Vial9taW5taXNzaW5nX3Bsb3QpCmBgYAoKCgojIyMgNC4gUmFuZG9tIERhdGEgUGFydGl0aW9ucwoKVG8gb3BlcmF0aW9uYWxpemUgaW5kaXZpZHVhbCBmb290cHJpbnRpbmcgcHJlZGljdGlvbiwgd2UgcmFuZG9tbHkgc3BsaXQgZWFjaCBpbmRpdmlkdWFsJ3MgYXZhaWxhYmxlIEdQUyBkYXRhIHRvIHR3byBoYWxmIHBhcnRpdGlvbnMgZm9yIGByIHBhcnRfdGltZXNgIHRpbWVzLiBXZSB1c2VkIHRoZSBmZWF0dXJlcyBmcm9tIHRoZSBmaXJzdCBoYWxmIHRvIGRldGVybWluZSBpZiB3ZSBjYW4gaWRlbnRpZnkgdGhlIHNhbWUgaW5kaXZpZHVhbCB1c2luZyB0aGVpciBkYXRhIGluIHRoZSBzZWNvbmQgdW5zZWVuIGhhbGYuIEFnYWluLCB0byBhdm9pZCBhbnkgKHVuKWx1Y2t5IHJhbmRvbSBzcGxpdHMsIHdlIHJlcGVhdGVkIGRhdGEgcGFydGl0aW9uIGByIHBhcnRfdGltZXNgIHRpbWVzLgoKYGBge3J9CnNldC5zZWVkKDUxMCkKbWFrZV9zdWJqX3NlcSA9IGZ1bmN0aW9uKGdwc19kZl9jbGVhbjIscGFydF90aW1lcyA9IDEwKSB7CiAgc3Vial9zZXEgPSBsaXN0KCkKICBmb3IgKHN1YmogaW4gdW5pcXVlKGdwc19kZl9jbGVhbjIkSUlEKSl7CiAgICBzdWJqX2RhdGEgPSBzdWJzZXQoZ3BzX2RmX2NsZWFuMiwgSUlEPT1zdWJqKQogICAgc3Vial9zZXFbW3N1YmpdXSA8LWNyZWF0ZURhdGFQYXJ0aXRpb24oc3Vial9kYXRhJElJRCx0aW1lcyA9IHBhcnRfdGltZXMsIHAgPTAuNSkKICB9CiAgcmV0dXJuKHN1Ympfc2VxKQp9CnN1Ympfc2VxID0gbWFrZV9zdWJqX3NlcShncHNfZGZfY2xlYW4yLCAxMDApCgpgYGAKCgoKIyMjIDUuIEJ1aWxkIENvcnJlbGF0aW9uIE1hdHJpeAoKSGVyZSwgc2ltaWxhciB0byBbRmlubiBldCBhbC4gTmF0IE5ldXJvICAoMjAxNSldKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbm4uNDEzNSkgYW5kIFtLYXVmbWFubiBldCBhbC4gTmF0IE5ldXJvICgyMDE1KV0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9zNDE1OTMtMDE5LTA0NzEtNyksIHdlIGJ1aWx0IGEgUGVhcnNvbiBjb3JyZWxhdGlvbiBtYXRyaXggYW1vbmcgYHIgbGVuZ3RoKGNvbG5hbWVzKGdwc19kZl9jbGVhbjIpKS0yYCBhdmFpbGFibGUgR1BTIGZlYXR1cmVzLiBCZWxvdyBpcyBhbiBpbGx1c3RyYXRpdmUgc2FtcGxlIG9mIHRoZSB2YXJpYWJsZXMgYW5kIHRoZWlyIGNvcnJlbGF0aW9ucy4KCmBgYHtyIGV4YW1wbGVfY29yX2ZpZywgZmlnLndpZHRoPTMsIGZpZy53aWR0aD0zLCBmaWcuYWxpZ249ImNlbnRlciJ9CiMgYW4gZXhhbXBsZSBvZiBzdWJqIDEsIGFuZCBmaXJzdCBoYWxmCmV4YW1wbGVfZGF0YSA9IGdwc19kZl9jbGVhbjJbc3Vial9zZXEkYDE0dzVxbG84YCRSZXNhbXBsZTAwMSwzOjE3XQpncHNfY29yID0gcnF1ZXJ5LmNvcm1hdChleGFtcGxlX2RhdGEsIHR5cGUgPSAiZnVsbCIpCgpgYGAKKipGaWd1cmUgNjogQ29ycmVsYXRpb24gbWF0cml4IG9mIEdQUyBmZWF0dXJlcyBmb3IgYSBzdWJqZWN0LioqCgoKV2UgdGhlbiBjYWxjdWxhdGVkIHRoZSBmZWF0dXJlIG1hdHJpeCBmb3IgYWxsIGByIGxlbmd0aCh1bmlxdWUoZ3BzX2RmX2NsZWFuMiRJSUQpKWAgc3ViamVjdHMgaW4gdGhlIGRhdGFzZXQsIHNlcGVyYXRlbHkgZm9yIGVhY2ggaGFsZiBkYXRhIHBhcnRpdGlvbiwgYW5kIGZvciBlYWNoIHJhbmRvbSBzcGxpdC4gCgpgYGB7ciBjcmVhdGUgZmVhdHVyZSBtYXRyaXggZm9yIGV2ZXJ5b25lLCB3YXJuaW5nPUZBTFNFfQptYWtlX2ZlYXR1cmVfbWF0cml4ID0gZnVuY3Rpb24oZ3BzX2RmLCBzdWJqX3NlcSkgewogIHN1YmpfbWF0XzEgPSBsaXN0KCkKICBzdWJqX21hdF8yID0gbGlzdCgpCiAgZm9yIChzdWJqIGluIHVuaXF1ZShncHNfZGYkSUlEKSl7CiAgICBzdWJqX2RhdGEgPSBzdWJzZXQoZ3BzX2RmX2NsZWFuMiwgSUlEPT1zdWJqKQogICAgc3Vial9tYXRfMVtbc3Vial1dID0gbGFwcGx5KHN1Ympfc2VxW1tzdWJqXV0sIGZ1bmN0aW9uKGxpc3QpIHJxdWVyeS5jb3JtYXQoc3Vial9kYXRhW2xpc3QsMzoxN10sIHR5cGUgPSAiZmxhdHRlbiIsIGdyYXBoID0gRikkcikKICAgIHN1YmpfbWF0XzJbW3N1YmpdXSA9IGxhcHBseShzdWJqX3NlcVtbc3Vial1dLCBmdW5jdGlvbihsaXN0KSBycXVlcnkuY29ybWF0KHN1YmpfZGF0YVstbGlzdCwzOjE3XSwgdHlwZSA9ICJmbGF0dGVuIiwgZ3JhcGggPSBGKSRyKQogIH0KICByZXR1cm4obGlzdChzdWJqX21hdF8xID0gc3Vial9tYXRfMSwgc3Vial9tYXRfMiA9IHN1YmpfbWF0XzIgKSkKfQoKZ3BzX2NsZWFuMl9mZWF0dXJlID0gbWFrZV9mZWF0dXJlX21hdHJpeChncHNfZGZfY2xlYW4yKQpgYGAKCmBgYHtyIHZpc3VhbGl6YXRpb24gb2YgZmVhdHVyZSBjb3JyZWxhdGlvbnMgYWNyb3NzIHN1YmplY3RzfQpncHNfd2lkZV9tYXRyaXggPSBkYXRhLmZyYW1lKCkKCmZvciAoc3ViaiBpbiBhcnJhbmdlKHN1YmpfZGF5cyxgRGF5cyBDb2xsZWN0ZWRgKSRJSUQpewogICNpZiAoKHN1YmogJWluJSBzdWJqX2RheXMkSUlEW3doaWNoKHN1YmpfZGF5cyRgRGF5cyBDb2xsZWN0ZWRgPjg1ICYgc3Vial9kYXlzJGBEYXlzIENvbGxlY3RlZGA8MTAwKV0pID09IFQpIHsKICAgIGZvciAocGFydCBpbiAxOjUpewogICAgICBncHNfd2lkZV9tYXRyaXggPSByYmluZChncHNfd2lkZV9tYXRyaXgsZ3BzX2NsZWFuMl9mZWF0dXJlJHN1YmpfbWF0XzFbW3N1YmpdXVtbcGFydF1dJGNvcikKICAgICAgZ3BzX3dpZGVfbWF0cml4ID0gcmJpbmQoZ3BzX3dpZGVfbWF0cml4LGdwc19jbGVhbjJfZmVhdHVyZSRzdWJqX21hdF8yW1tzdWJqXV1bW3BhcnRdXSRjb3IpCiAgICAgIH0KICAgICN9Cn0KCmdwc193aWRlX21hdHJpeCA9IHQoZ3BzX3dpZGVfbWF0cml4KQpiaWdfY29ycGxvdCA9IHJxdWVyeS5jb3JtYXQoZ3BzX3dpZGVfbWF0cml4LCB0eXBlID0gImZ1bGwiLGdyYXBoPUZBTFNFKQpsZXZlbHBsb3QoYmlnX2NvcnBsb3QkcixzY2FsZXM9bGlzdChkcmF3PUZBTFNFKSxjb2wucmVnaW9ucyA9IHJldihyYWluYm93KDEwMDApKVstYygxOjIwKV0sIHJlZ2lvbiA9VCwgeWxhYi5yaWdodCA9ICJQZWFyc29uIGNvcnJlbGF0aW9uIiwgbWFpbj1saXN0KGxhYmVsPSdHUFMgRmVhdHVyZSBTaW1pbGFyaXR5JykseGxhYj0iIix5bGFiPSIiKQpgYGAKCiMjIyA2LiBNYXRjaCBUYXJnZXQKCk5leHQgd2UgdHJpZWQgdG8gbWF0Y2ggZWFjaCB0YXJnZXQsIGRlZmluZWQgYnkgb25lIHN1YmplY3QncyBmZWF0dXJlIG1hdHJpeCBpbiB0aGUgZmlyc3QgaGFsZiwgdG8gYSBmZWF0dXJlIG1hdHJpeCBpbiB0aGUgc2Vjb25kIGhhbGYgaW4gdGhlIHNhbWUgcmFuZG9tIHNwbGl0LiBJZiB0aGUgc2FtZSBpbmRpdmlkdWFsJ3MgZmVhdHVyZSBtYXRyaXggaW4gdGhlIHNlY29uZCBoYWxmIGhhZCB0aGUgaGlnaGVzdCBjb3JyZWxhdGlvbiBhbW9uZyBhbGwgc3ViamVjdHMsIHRoZW4gd2UgYXNzaWduZWQgdGhlIG1hdGNoIHJlc3VsdCAxLCBpbmRpY2F0aW5nIGEgc3VjY3Vzc2Z1bCBtYXRjaCwgb3RoZXJ3aXNlIDAsIGluZGljYXRpbmcgYW4gdW5zY2N1c3NmdWwgbWF0Y2guCgpgYGB7ciBjYWxjIG1hdGNoIGNvcn0KIyBjcmVhdGluZyBhICJkYXRhYmFzZSIgYWdhaW5zdCB3aGljaCB0YXJnZXQgaXMgdG8gYmUgbWF0Y2hlZCB3aXRoCnN1YmpfbWF0XzEgPSBncHNfY2xlYW4yX2ZlYXR1cmUkc3Vial9tYXRfMQpzdWJqX21hdF8yID0gZ3BzX2NsZWFuMl9mZWF0dXJlJHN1YmpfbWF0XzIKCmNhbGNfbWF0Y2hfY29yID0gZnVuY3Rpb24oc3Vial9tYXRfMSxzdWJqX21hdF8yKSB7CiAgcGFydF90aW1lcyA9IGxlbmd0aChzdWJqX21hdF8xW1sxXV0pCiAgZGF0YWJhc2UgPSBsaXN0KCkgCiAgZm9yICh0aW1lIGluIDE6cGFydF90aW1lcykgewogICAgICBkYXRhYmFzZVtbdGltZV1dID0gbGFwcGx5KHN1YmpfbWF0XzIsIGZ1bmN0aW9uKHN1YmptYXQpIHN1YmptYXRbW3RpbWVdXSRjb3IpCiAgfQogIAogICAgIyBtYXRjaCB0YXJnZXQgdG8gZGF0YWJhc2UKICAgIG1hdGNoX2NvciA9IGxpc3QoKQogICAgZm9yIChzdWJqMSBpbiBuYW1lcyhzdWJqX21hdF8xKSl7ICNsb29wIHRocm91Z2ggZWFjaCBzdWJqCiAgICAgICMgY3JlYXRlIGEgbGlzdCBvZiB0YXJnZXQgYWNyb3NzIHBhcnRpdGlvbnMKICAgICAgdGFyZ2V0X2xpc3QgPSBsYXBwbHkoc3Vial9tYXRfMVtbc3ViajFdXSwgZnVuY3Rpb24ocGFydCkgcGFydCRjb3IpCiAgICAgICMgY3JlYXRlIGEgbWF0Y2ggbGlzdAogICAgICBmb3IgKHRpbWUgaW4gMTpwYXJ0X3RpbWVzKXsKICAgICAgICB0YXJnZXRfc3Vial90aW1lID0gdGFyZ2V0X2xpc3RbW3RpbWVdXSAjbG9vcCB0aHJvdWdoIGVhY2ggcGFydGl0aW9uCiAgICAgICAgZm9yIChzdWJqMiBpbiBuYW1lcyhzdWJqX21hdF8yKSl7ICNsb29wIGV2ZXJ5b25lIGluIDJuZCBoYWxmCiAgICAgICAgICBkYXRhX3N1YmpfdGltZSA9IHN1YmpfbWF0XzJbW3N1YmoyXV1bW3RpbWVdXSRjb3IKICAgICAgICAgIG1hdGNoX2Nvcltbc3ViajFdXVtbYXMuY2hhcmFjdGVyKHRpbWUpXV1bW3N1YmoyXV0gPSBjb3IodGFyZ2V0X3N1YmpfdGltZSxkYXRhX3N1YmpfdGltZSx1c2UgPSAibmEub3IuY29tcGxldGUiKQogICAgICAgIH0KICAgICAgfQogICAgfQogIHJldHVybihtYXRjaF9jb3IpCn0KCm1hdGNoX2NvciA9IGNhbGNfbWF0Y2hfY29yKHN1YmpfbWF0XzEsc3Vial9tYXRfMikKCmBgYAoKCkJ5IHRhbGx5aW5nIHVwIGFsbCB0aGUgc3VjY2Vzc2Z1bCBhbmQgdW5zdWNjZXNzZnVsIG1hdGNocyBpbiBlYWNoIG9mIHRoZSBgciBwYXJ0X3RpbWVzYCByYW5kb20gc3BsaXRzLCB3ZSBjYWxjdWxhdGVkIGEgZGlzdHJpYnV0aW9uIG9mIG1hdGNoIGFjY3VyYWN5LgoKYGBge3IgY2FsYyBhY2N1cmFjeSBieSBwYXJ0aXRpb259CmNhbGNfYWNjX3RpbWU9ZnVuY3Rpb24obWF0Y2hfY29yLHBhcnRfdGltZXMsIG1ldGhvZCA9ICJtYXgiKSB7CiAgYWNjX3RpbWUgPSBhcnJheSgpCiAgZm9yICh0aW1lIGluIDE6cGFydF90aW1lcyl7CiAgICBhY2NfdGltZVt0aW1lXSA9IDAKICAgIGZvciAoc3ViaiBpbiBuYW1lcyhzdWJqX21hdF8xKSl7CiAgICAgIGlmIChtZXRob2QgPT0gIm1heCIpewogICAgICAgIHBvc2l0aW9uID0gd2hpY2gubWF4KHVubGlzdChtYXRjaF9jb3JbW3N1YmpdXVtbYXMuY2hhcmFjdGVyKHRpbWUpXV0pKQogICAgICB9IAogICAgICBlbHNlIGlmIChtZXRob2QgPT0gIm1pbiIpewogICAgICAgIHBvc2l0aW9uID0gd2hpY2gubWluKHVubGlzdChtYXRjaF9jb3JbW3N1YmpdXVtbYXMuY2hhcmFjdGVyKHRpbWUpXV0pKQogICAgICB9CiAgICAgIAogICAgICBwcmVkaWN0ZWRfc3ViaiA9IG5hbWVzKHN1YmpfbWF0XzEpW3Bvc2l0aW9uXQogICAgICBpZiAocHJlZGljdGVkX3N1YmogPT0gc3ViaikgewogICAgICAgIGFjY190aW1lW3RpbWVdID0gYWNjX3RpbWVbdGltZV0gKyAxCiAgICAgIH0KICAgIH0KICB9CiAgYWNjX3RpbWUgPSBhY2NfdGltZS9sZW5ndGgobmFtZXMoc3Vial9tYXRfMSkpCiAgcmV0dXJuKGFjY190aW1lKQp9CmFjY190aW1lID0gY2FsY19hY2NfdGltZShtYXRjaF9jb3IscGFydF90aW1lcykKYGBgCgpPdmVyIHRoZSBgciBwYXJ0X3RpbWVzYCByYW5kb20gc3BsaXRzLCB0aGUgbWVhbiBtYXRjaCBhY2N1cmFjeSB3YXMgYHIgcm91bmQobWVhbihhY2NfdGltZSksNCkqMTAwYCUsIHdpdGggYSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgYHIgcm91bmQoc2QoYWNjX3RpbWUpLDQpKjEwMGAlLCBoaWdoIG9mIGByIHJvdW5kKG1heChhY2NfdGltZSksNCkqMTAwYCUsIGFuZCBsb3cgb2YgYHIgcm91bmQobWluKGFjY190aW1lKSw0KSoxMDBgJSwgKDk1JUNJOiBgciByb3VuZChDSShhY2NfdGltZSlbJ2xvd2VyJ10sMilgLWByIHJvdW5kKENJKGFjY190aW1lKVsndXBwZXInXSwyKWApLgoKYGBge3IgcGxvdCBhY2N1cmFjeSBieSBwYXJ0aXRpb24gaGlzdG9ncmFtLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQpwX3RpbWVfY29yID0gaGlzdF9jaHgoYWNjX3RpbWUsIGJpbnMgPSAxNSwgdGl0bGUgPSBwYXN0ZSgiQ29ycmVsYXRlZCBGZWF0dXJlczogXG4gQXZlcmFnZSBBY2N1cmFjeSBBY3Jvc3MiLHBhcnRfdGltZXMsIkRhdGEgUGFydGl0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQpnZ3Bsb3RseShwX3RpbWVfY29yKQpgYGAKKipGaWd1cmUgNzogTWF0Y2ggYWNjdXJhY3kgZGlzdHJpYnV0b24gYWNyb3NzIGByIHBhcnRfdGltZXNgIGRhdGEgc3BsaXRzLioqIE1lYW4gbWF0Y2ggYWNjdXJhY3kgd2FzIGByIHJvdW5kKENJKGFjY190aW1lKVsnbWVhbiddLDIpYCAoOTUlQ0k6IGByIHJvdW5kKENJKGFjY190aW1lKVsnbG93ZXInXSwyKWAtYHIgcm91bmQoQ0koYWNjX3RpbWUpWyd1cHBlciddLDIpYCkKCgpgYGB7ciBjYWxjIGFjY3VyYWN5IGJ5IHN1Ymp9CmNhbGNfYWNjX3N1YmogPSBmdW5jdGlvbihtYXRjaF9jb3IsIHBhcnRfdGltZXMsIG1ldGhvZCA9ICJtYXgiKXsKICBhY2Nfc3ViaiA9IGFycmF5KCkKICBmb3IgKHN1YmogaW4gbmFtZXMoc3Vial9tYXRfMSkpewogICAgYWNjX3N1Ympbc3Vial0gPSAwCiAgICBmb3IgKHRpbWUgaW4gMTpwYXJ0X3RpbWVzKXsKICAgICAgaWYgKG1ldGhvZCA9PSAibWF4Iil7CiAgICAgICAgcG9zaXRpb24gPSB3aGljaC5tYXgodW5saXN0KG1hdGNoX2Nvcltbc3Vial1dW1thcy5jaGFyYWN0ZXIodGltZSldXSkpCiAgICAgIH0gCiAgICAgIGVsc2UgaWYgKG1ldGhvZCA9PSAibWluIil7CiAgICAgICAgcG9zaXRpb24gPSB3aGljaC5taW4odW5saXN0KG1hdGNoX2Nvcltbc3Vial1dW1thcy5jaGFyYWN0ZXIodGltZSldXSkpCiAgICAgIH0KICAgICAgcHJlZGljdGVkX3N1YmogPSBuYW1lcyhzdWJqX21hdF8xKVtwb3NpdGlvbl0KICAgICAgaWYgKHByZWRpY3RlZF9zdWJqID09IHN1YmopIHsKICAgICAgICBhY2Nfc3VialtzdWJqXSA9IGFjY19zdWJqW3N1YmpdICsgMQogICAgICB9CiAgICB9CiAgfQogIGFjY19zdWJqID0gYWNjX3N1YmovcGFydF90aW1lcwogIGFjY19zdWJqID0gYWNjX3N1YmpbLTFdCiAgcmV0dXJuKGFjY19zdWJqKQp9CmFjY19zdWJqID0gY2FsY19hY2Nfc3ViaihtYXRjaF9jb3IsIHBhcnRfdGltZXMpCmBgYAoKCkluIGFkZGl0aW9uIHRvIHRoZSBtYXRjaCBhY2N1cmFjeSBhY3Jvc3Mgc3ViamVjdHMsIHdlIGFsc28gY2FsY3VsYXRlZCB0aGUgbWF0Y2ggYWNjdXJhY3kgZm9yIGVhY2ggaW5kaXZpZHVhbC4gQWNyb3NzIHRoZSBgciBsZW5ndGgodW5pcXVlKGdwc19kZl9jbGVhbjIkSUlEKSlgIHN1YmplY3RzLCB0aGUgbWVhbiBtYXRjaCBhY2N1cmFjeSB3YXMgYHIgcm91bmQobWVhbihzdWJqX2RmJHkpLDQpKjEwMGAlLCB3aXRoIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIGByIHJvdW5kKHNkKHN1YmpfZGYkeSksNCkqMTAwYCUsIGhpZ2ggb2YgYHIgcm91bmQobWF4KHN1YmpfZGYkeSksNCkqMTAwYCUsIGFuZCBsb3cgb2YgYHIgcm91bmQobWluKHN1YmpfZGYkeSksNCkqMTAwYCUuCgpgYGB7ciBwbG90IGFjY3VyYWN5IGJ5IHN1YmplY3RzLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQpzdWJqX3NjYXR0ZXIgID0gZnVuY3Rpb24oZ3BzX2RmLCBhY2Nfc3Vial92ZWN0b3IsIG1ldGhvZCl7CiAgc3Vial9kZiA8LSBkYXRhLmZyYW1lKHg9dW5pcXVlKGdwc19kZiRJSUQpKQogIHN1YmpfZGYkeSA9IGFjY19zdWJqX3ZlY3RvcgogIHN1YmpfZGYgPSBzdWJqX2RmW29yZGVyKHN1YmpfZGYkeSksXQogIHN1YmpfYWNjX3Bsb3QgPSBnZ3Bsb3Qoc3Vial9kZikgKyAKICBnZW9tX3BvaW50KGFlcyh4ID0gcmVvcmRlcih4LCB5KSwgeSA9IHkpKSArIAogICB0aGVtZV9jb3dwbG90KCkgKyAKICAgIGxhYnModGl0bGUgPSBwYXN0ZShtZXRob2QsIkZlYXR1cmVzIFxuIFN1YmplY3QgTGV2ZWwgQWNjdXJhY3kgQWNyb3NzIixwYXJ0X3RpbWVzLCJEYXRhIFBhcnRpdGlvbnMiKSwgCiAgICAgICB4ID0gIlN1YmplY3RzIiwgeSA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IikgKwogICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIHNpemUgPSA4KSwgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgcmV0dXJuKHN1YmpfYWNjX3Bsb3QpCn0KCmdncGxvdGx5KHN1Ympfc2NhdHRlcihncHNfZGZfY2xlYW4yLGFjY19zdWJqLCAiQ29ycmVsYXRlZCIpKQpgYGAKKipGaWd1cmUgODogTWF0Y2ggYWNjdXJhY3kgZGlzdHJpYnV0b24gYWNyb3NzIGByIGxlbmd0aCh1bmlxdWUoZ3BzX2RmX2NsZWFuMiRJSUQpKWAgc3ViamVjdHMuKiogTWVhbiBtYXRjaCBhY2N1cmFjeSB3YXMgYHIgcm91bmQobWVhbihzdWJqX2RmJHkpLDQpKjEwMGAlLCB3aXRoIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIGByIHJvdW5kKHNkKHN1YmpfZGYkeSksNCkqMTAwYCUsIGhpZ2ggb2YgYHIgcm91bmQobWF4KHN1YmpfZGYkeSksNCkqMTAwYCUsIGFuZCBsb3cgb2YgYHIgcm91bmQobWluKHN1YmpfZGYkeSksNCkqMTAwYCUuIFRoaXMgc2hvd3MgZHJhbWF0aWMgZGlmZmVyZW5jZXMgaW4gaW5kaXZpZHVhbCAqZm9vdHByaW50aW5nIGRpc3RpbmN0aXZlbmVzcyouCgoKIyMjIDcuIFBlcm11dGF0aW9uIFRlc3QKClRvIGFzc2VzcyB0aGUgc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIG9mIHRoZSByZXN1bHRzIGFib3ZlLCB3ZSBjb25kdWN0ZWQgYSBub24tcGFyYW1ldHJpYyBwZXJtdXRhdGlvbiB0ZXN0LiBUbyBkbyB0aGlzLCB3ZSByYW5kb21seSBzY3JhbWJsZWQgcGFpci13aXNlIHN1YmplY3QtdG8tZGF5IGxpbmthZ2UuIFdlIHJlcGVhdGVkIHRoaXMgcHJvY2VzcyBgciBwZXJtX3RpbWVgIHRpbWVzLCBhbmQgZm9sbG93ZWQgdGhlIHNhbWUgcHJvY2VkdXJlIGFzIGFib3ZlIHRvIG9idGFpbiBhIG51bGwgZGlzdHJpYnV0aW9uIG9mIGF2ZXJhZ2UgbWF0Y2ggYWNjdXJhY3kgYWNyb3NzIHNhbXBsZSwgYW5kIGZvciBlYWNoIGluZGl2aWR1YWwuCgpgYGB7ciBjYWxjIHBlcm11dGF0aW9uIGFjY3VyYWN5LCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cmdwc19kZl9wZXJtID0gZ3BzX2RmX2NsZWFuMgpwZXJtX3RpbWUgPSAxMDAwCnBlcm1fYWNjX3RpbWUgPSBsaXN0KCkKcGVybV9hY2Nfc3ViaiA9IGxpc3QoKQpmb3IgKGkgaW4gMTpwZXJtX3RpbWUpIHsKICBwZXJtX3BhcnRfdGltZXMgPSAxCiAgcHJpbnQocGFzdGUoInByb2Nlc3NpbmcgLi4uIiwgaSwiLi4uIikpCiAgZ3BzX2RmX3Blcm0kSUlEID0gc2FtcGxlKGdwc19kZl9wZXJtJElJRCkKICBwZXJtX3N1Ympfc2VxID0gbWFrZV9zdWJqX3NlcShncHNfZGZfcGVybSxwYXJ0X3RpbWVzID0gcGVybV9wYXJ0X3RpbWVzKQogIHBlcm1fZ3BzID0gbWFrZV9mZWF0dXJlX21hdHJpeChncHNfZGZfcGVybSxwZXJtX3N1Ympfc2VxKQogIHBlcm1fbWF0XzEgPSBwZXJtX2dwcyRzdWJqX21hdF8xCiAgcGVybV9tYXRfMiA9IHBlcm1fZ3BzJHN1YmpfbWF0XzIKICBwZXJtX21hdGNoX2NvciA9IGNhbGNfbWF0Y2hfY29yKHBlcm1fbWF0XzEscGVybV9tYXRfMikKICBwZXJtX2FjY190aW1lW1tpXV0gPSBjYWxjX2FjY190aW1lKHBlcm1fbWF0Y2hfY29yLHBhcnRfdGltZXMgPSBwZXJtX3BhcnRfdGltZXMpCiAgcGVybV9hY2Nfc3VialtbaV1dID0gY2FsY19hY2Nfc3ViaihwZXJtX21hdGNoX2NvcixwYXJ0X3RpbWVzID0gcGVybV9wYXJ0X3RpbWVzKQp9CmBgYAoKCk92ZXIgdGhlIGByIHBlcm1fdGltZWAgcGVybXV0YXRpb25zLCB0aGUgbWVhbiBtYXRjaCBhY2N1cmFjeSB3YXMgYHIgcm91bmQobWVhbihwZXJtX2FjY190aW1lX2FsbCksNCkqMTAwYCUsIHdpdGggYSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgYHIgcm91bmQoc2QocGVybV9hY2NfdGltZV9hbGwpLDQpKjEwMGAlLCBoaWdoIG9mIGByIG1heChtZWFuKHBlcm1fYWNjX3RpbWVfYWxsKSw0KSoxMDBgJSwgYW5kIGxvdyBvZiBgciBtaW4obWVhbihwZXJtX2FjY190aW1lX2FsbCksNCkqMTAwYCUuKDk1JUNJOiBgciByb3VuZChDSShwZXJtX2FjY190aW1lX2FsbClbJ2xvd2VyJ10sMilgLWByIHJvdW5kKENJKHBlcm1fYWNjX3RpbWVfYWxsKVsndXBwZXInXSwyKWApCgpgYGB7ciBjb21iaW5lIHBhcnRpdGlvbiBwZXJtdXRhdGlvbiByZXN1bHRzLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQpwZXJtX2FjY190aW1lX2FsbCA9IHVubGlzdChwZXJtX2FjY190aW1lKQpxX3RpbWVfY29yID0gaGlzdF9jaHgocGVybV9hY2NfdGltZV9hbGwsIGJpbnMgPSA4LCB0aXRsZSA9IHBhc3RlKCJBdmVyYWdlIEFjY3VyYWN5IEFjcm9zcyIsbGVuZ3RoKHBlcm1fYWNjX3RpbWVfYWxsKSwiUGVybXV0YXRpb25zIiksIHhheGlzID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiLCB5YXhpcyA9ICJDb3VudCIpCmdncGxvdGx5KHFfdGltZV9jb3IpCmBgYAoqKkZpZ3VyZSA5OiBNYXRjaCBhY2N1cmFjeSBkaXN0cmlidXRvbiBhY3Jvc3MgYHIgcGVybV90aW1lYCBwZXJtdXRhdGlvbnMuKiogTWVhbiBtYXRjaCBhY2N1cmFjeSBpbiBwZXJtdXRhdGlvbiB3YXMgYHIgcm91bmQoQ0kocGVybV9hY2NfdGltZV9hbGwpWydtZWFuJ10sMilgICg5NSVDSTogYHIgcm91bmQoQ0kocGVybV9hY2NfdGltZV9hbGwpWydsb3dlciddLDIpYC1gciByb3VuZChDSShwZXJtX2FjY190aW1lX2FsbClbJ3VwcGVyJ10sMilgKS4gVGhpcyBudWxsIGRpc3RyaWJ1dGlvbiBkb2VzIG5vdCBvdmVybGFwcCB3aXRoIHRoZSBkaXN0cmlidXRvbiB1c2luZyByZWFsIGRhdGEgKCoqRmlnLjcqKikgYXQgYWxsLgoKCldlIGFsc28gY2FsY3VsYXRlZCBudWxsIGRpc3RyaWJ1dGlvbiBvZiBtYXRjaCBhY2N1cmFjeSBmb3IgZWFjaCBzdWJqZWN0LiBPdmVyIHRoZSBgciBwZXJtX3RpbWVgIHBlcm11dGF0aW9ucywgdGhlIG1lYW4gbWF0Y2ggYWNjdXJhY3kgd2FzIGByIHJvdW5kKG1lYW4oc3Vial9kZiR5X3Blcm0pLDQpKjEwMGAlLCB3aXRoIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIGByIHJvdW5kKHNkKHN1YmpfZGYkeV9wZXJtKSw0KSoxMDBgJSwgaGlnaCBvZiBgciByb3VuZChtYXgoc3Vial9kZiR5X3Blcm0pLDQpKjEwMGAlLCBhbmQgbG93IG9mIGByIHJvdW5kKG1pbihzdWJqX2RmJHlfcGVybSksNCkqMTAwYCUuCgpgYGB7ciBjb21iaW5lIHN1YmogcGVybXV0YXRpb24gYW5hbHlzaXN9CnBlcm1fc3ViaiA9IGxpc3QoKQpmb3IgKHN1YmogaW4gc3Vial9hY2NfcGxvdCRkYXRhJHgpIHsKICBwZXJtX3N1YmokdmFsW1tzdWJqXV0gPSBzYXBwbHkocGVybV9hY2Nfc3ViaiwgZnVuY3Rpb24ocGVybSkgcGVybVt3aGljaChuYW1lcyhwZXJtKSA9PSBzdWJqKV0pCiAgcGVybV9zdWJqJGhpc3RbW3N1YmpdXSA9IGhpc3RfY2h4KHBlcm1fc3Vialtbc3Vial1dJHZhbCwgYmlucyA9IDgsIHRpdGxlID0gcGFzdGUoc3ViaiwiOiBBY2N1cmFjeSBBY3Jvc3MgXG4iLHBlcm1fdGltZSwiUGVybXV0YXRpb25zIiksIHhheGlzID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiLCB5YXhpcyA9ICJDb3VudCIpCiAgcGVybV9zdWJqJGFjY1tbc3Vial1dID0gc3VtKHBlcm1fc3ViaiR2YWxbW3N1YmpdXSkvcGVybV90aW1lCn0KYGBgCgpgYGB7ciBwbG90IHN1YmogYW5hbHlzaXMgd2l0aCBwZXJtLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQpzdWJqX2RmIDwtIGRhdGEuZnJhbWUoeD11bmlxdWUoZ3BzX2RmX2NsZWFuMiRJSUQpKQpzdWJqX2RmJHkgPSBhY2Nfc3ViagpzdWJqX2RmJHlfcGVybSA9IHVubGlzdChwZXJtX3N1YmokYWNjKQpzdWJqX2RmID0gc3Vial9kZltvcmRlcihzdWJqX2RmJHkpLF0KcF9zdWJqX2Nvcl9wZXJtID0gZ2dwbG90KHN1YmpfZGYsIGFlcyh4ID0gcmVvcmRlcih4LCB5KSwgeSA9IHZhbHVlKSkgKyAKICBnZW9tX3BvaW50KGFlcyh5ID0geSwgY29sID0gInN1YmplY3QgZGF0YSIpKSArIAogIGdlb21fcG9pbnQoYWVzKHkgPSB5X3Blcm0sIGNvbCA9ICJwZXJtdXRhdGlvbiIpKSArCiAgdGhlbWVfY293cGxvdCgpICsgCiAgbGFicyh0aXRsZSA9IHBhc3RlKCJDb3IgRmVhdHVyZXMgXG4gU3ViamVjdCBMZXZlbCBBY2N1cmFjeSBBY3Jvc3MiLHBhcnRfdGltZXMsIkRhdGEgUGFydGl0aW9ucyIpLCAKICAgICAgIHggPSAiU3ViamVjdHMiLCB5ID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCBzaXplID0gOCksIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpnZ3Bsb3RseShwX3N1YmpfY29yX3Blcm0pCmBgYAoqKkZpZ3VyZSA4OiBNYXRjaCBhY2N1cmFjeSBudWxsIGRpc3RyaWJ1dG9uIGFjcm9zcyBgciBsZW5ndGgodW5pcXVlKGdwc19kZl9jbGVhbjIkSUlEKSlgIHN1YmplY3RzLioqIE1lYW4gbWF0Y2ggYWNjdXJhY3kgaW4gcGVybXV0YXRpb24gd2FzIGByIHJvdW5kKG1lYW4oc3Vial9kZiR5X3Blcm0pLDQpKjEwMGAlLCB3aXRoIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIGByIHJvdW5kKHNkKHN1YmpfZGYkeV9wZXJtKSw0KSoxMDBgJSwgaGlnaCBvZiBgciByb3VuZChtYXgoc3Vial9kZiR5X3Blcm0pLDQpKjEwMGAlLCBhbmQgbG93IG9mIGByIHJvdW5kKG1pbihzdWJqX2RmJHlfcGVybSksNCkqMTAwYCUuIFdoaWxlIGluZGl2aWR1YWxzIGV4aGliaXRlZCBtYXJrZWQgZGlmZmVyZW5jZXMgaW4gZm9vdHByaW50aW5nIGRpc3RpbmN0aXZlbnNzLCB0aGUgc3ViamVjdCB3aXRoIHRoZSBsb3dlc3QgcHJlZGljdGlvbiBhY2N1cmFjeSB3YXMgc3RpbGwgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBhZ2FpbnN0IHRoZSBwZXJtdXRhdGlvbiB0ZXN0IChpLmUuIGZvciBzdWJqZWN0IGByIHN1YmpfZGYkeFsxXWA6IGRhdGE6IGByIHJvdW5kKG1pbihzdWJqX2RmJHkpLDQpKjEwMGAgJSB2cy4gcGVybXV0YXRpb246IGByIHJvdW5kKG1pbihzdWJqX2RmJHlfcGVybSksNCkqMTAwYCUpLgoKCiMjIyA4LiBNZWFuIEZlYXR1cmVzCgpXZSBjYWxjdWxhdGVkIHRoZSBgbWVhbmAgb2YgZWFjaCBHUFMgZmVhdHVyZSBhcyBhIG1ldHJpYyBvZiBmZWF0dXJlIHZhcmlhYmlsaXR5LiBTaW1pbGFyIHRvIGFib3ZlLCB3ZSBjYWxjdWxhdGVkIGBtZWFuYCBzZXBhcmF0ZWx5IGZvciBlYWNoIGRhdGEgcGFydGl0aW9uIGFuZCBmb3IgZWFjaCBpbmRpdmlkdWFsLgoKYGBge3IgY2FsYyBtZWFuIEdQUyBmZWF0dXJlc30KbWVhbl9ncHMgPSBmdW5jdGlvbihncHNfZGYpewogIG1lYW5fbGlzdCA9IHNhcHBseSgzOjE3LGZ1bmN0aW9uKGkpIG1lYW4oZ3BzX2RmWyxpXSxuYS5ybT1UKSkKICBuYW1lcyhtZWFuX2xpc3QpID0gY29sbmFtZXMoZ3BzX2RmKVszOjE3XQogIHJldHVybihtZWFuX2xpc3QpCn0KCm1ha2VfZmVhdHVyZV9tZWFuID0gZnVuY3Rpb24oZ3BzX2RmLCBzdWJqX3NlcSkgewogIHN1YmpfbWF0XzEgPSBsaXN0KCkKICBzdWJqX21hdF8yID0gbGlzdCgpCiAgZm9yIChzdWJqIGluIHVuaXF1ZShncHNfZGYkSUlEKSl7CiAgICBzdWJqX2RhdGEgPSBzdWJzZXQoZ3BzX2RmX2NsZWFuMiwgSUlEPT1zdWJqKQogICAgc3Vial9tYXRfMVtbc3Vial1dID0gbGFwcGx5KHN1Ympfc2VxW1tzdWJqXV0sIGZ1bmN0aW9uKGxpc3QpIG1lYW5fZ3BzKHN1YmpfZGF0YVtsaXN0LF0pKQogICAgc3Vial9tYXRfMltbc3Vial1dID0gbGFwcGx5KHN1Ympfc2VxW1tzdWJqXV0sIGZ1bmN0aW9uKGxpc3QpIG1lYW5fZ3BzKHN1YmpfZGF0YVstbGlzdCxdKSkKICB9CiAgcmV0dXJuKGxpc3Qoc3Vial9tYXRfMSA9IHN1YmpfbWF0XzEsIHN1YmpfbWF0XzIgPSBzdWJqX21hdF8yICkpCn0KCmdwc19jbGVhbjJfbWVhbl9mZWF0ID0gbWFrZV9mZWF0dXJlX21lYW4oZ3BzX2RmX2NsZWFuMiwgc3Vial9zZXEpCmBgYAoKYGBge3Igd2FycCBtZWFuIGZlYXR1cmUgbGlzdCB0byBkYXRhIGZyYW1lLCBmaWcuaGVpZ2h0PTYsIGVjaG89Rn0KbWVhbl9kZiA9IGxpc3QoKQpmb3IgKHN1YmogaW4gdW5pcXVlKGdwc19kZiRJSUQpKSB7CiAgbWVhbl9kZltbc3Vial1dID0gZGF0YS5mcmFtZShtYXRyaXgoTkEsIHBhcnRfdGltZXMsIGxlbmd0aChjb2xuYW1lcyhncHNfZGZfY2xlYW4pKS0yKSkKICBmb3IgKGkgaW4gMTpwYXJ0X3RpbWVzKXsKICAgIG1lYW5fZGZbW3N1YmpdXVtpLF0gPSBncHNfY2xlYW4yX21lYW5fZmVhdCRzdWJqX21hdF8xW1tzdWJqXV1bW2ldXQogIH0KICBjb2xuYW1lcyhtZWFuX2RmW1tzdWJqXV0pID0gY29sbmFtZXMoZ3BzX2RmX2NsZWFuWzM6MTddKQp9CgptZWFuX3Bsb3RzID0gbGlzdCgpCmZvciAoZ3BzX2Z0IGluIGNvbG5hbWVzKGdwc19kZl9jbGVhblszOjE3XSkpewogIG1lYW5fcGxvdHNbW2dwc19mdF1dID0gaGlzdF9jaHgoc2FwcGx5KG1lYW5fZGYsIGZ1bmN0aW9uKGRmKSBkZlsxLGdwc19mdF0pLCB0aXRsZSA9IGdwc19mdCwgeGF4aXMgPSAiRmVhdHVyZSBNZWFuIiwgeWF4aXMgPSAiY291bnQiKQp9CgpSZWR1Y2UoYCtgLCBtZWFuX3Bsb3RzKQpgYGAKKipGaWd1cmUgOTogRmVhdHVyZSBtZWFuIGFjcm9zcyBhbGwgc3ViamVjdHMqKi4gVGhlIGhpc3RvZ3JhbSBhcmUgZm9yIGRhdGEgaW4gdGhlIGZpcnN0IHJhbmRvbSBoYWxmIHNwbGl0LgoKCgojIyMgOS4gVmFyaWFiaWxpdHkgRmVhdHVyZXMKV2UgY2FsY3VsYXRlZCB0aGUgYHJvb3QgbWVhbiBzcXVhcmUgb2Ygc3VjY2Vzc2l2ZSBkaWZmZXJlbmNlc2Agb3IgYFJNU1NEYCBvZiBlYWNoIEdQUyBmZWF0dXJlIGFzIGEgbWV0cmljIG9mIGZlYXR1cmUgdmFyaWFiaWxpdHkuIFNpbWlsYXIgdG8gYWJvdmUsIHdlIGNhbGN1bGF0ZWQgYFJNU1NEYCBzZXBhcmF0ZWx5IGZvciBlYWNoIGRhdGEgcGFydGl0aW9uIGFuZCBmb3IgZWFjaCBpbmRpdmlkdWFsLgoKYGBge3IgY2FsY3VsYXRlIHZhcmlhYmlsaXR5IGZlYXR1cmVzfQpybXNzZF9ncHMgPSBmdW5jdGlvbihncHNfZGYpewogIHJtc3NkX2xpc3QgPSBzYXBwbHkoMzoxNyxmdW5jdGlvbihpKSBybXNzZF9pZChncHNfZGZbLGldLCBncHNfZGYkSUlELGxvbmc9RikpCiAgbmFtZXMocm1zc2RfbGlzdCkgPSBjb2xuYW1lcyhncHNfZGYpWzM6MTddCiAgcmV0dXJuKHJtc3NkX2xpc3QpCn0KCm1ha2VfZmVhdHVyZV9ybXNzZCA9IGZ1bmN0aW9uKGdwc19kZiwgc3Vial9zZXEpIHsKICBzdWJqX21hdF8xID0gbGlzdCgpCiAgc3Vial9tYXRfMiA9IGxpc3QoKQogIGZvciAoc3ViaiBpbiB1bmlxdWUoZ3BzX2RmJElJRCkpewogICAgc3Vial9kYXRhID0gc3Vic2V0KGdwc19kZl9jbGVhbjIsIElJRD09c3ViaikKICAgIHN1YmpfbWF0XzFbW3N1YmpdXSA9IGxhcHBseShzdWJqX3NlcVtbc3Vial1dLCBmdW5jdGlvbihsaXN0KSBybXNzZF9ncHMoc3Vial9kYXRhW2xpc3QsXSkpCiAgICBzdWJqX21hdF8yW1tzdWJqXV0gPSBsYXBwbHkoc3Vial9zZXFbW3N1YmpdXSwgZnVuY3Rpb24obGlzdCkgcm1zc2RfZ3BzKHN1YmpfZGF0YVstbGlzdCxdKSkKICB9CiAgcmV0dXJuKGxpc3Qoc3Vial9tYXRfMSA9IHN1YmpfbWF0XzEsIHN1YmpfbWF0XzIgPSBzdWJqX21hdF8yICkpCn0KCmdwc19jbGVhbjJfcm1zc2RfZmVhdCA9IG1ha2VfZmVhdHVyZV9ybXNzZChncHNfZGZfY2xlYW4yLCBzdWJqX3NlcSkKYGBgCgpgYGB7ciB3YXJwIHZhciBmZWF0dXJlIGxpc3QgdG8gZGF0YSBmcmFtZSwgZmlnLmhlaWdodD02LCBlY2hvPUZ9CnZhcl9kZiA9IGxpc3QoKQpmb3IgKHN1YmogaW4gdW5pcXVlKGdwc19kZiRJSUQpKSB7CiAgdmFyX2RmW1tzdWJqXV0gPSBkYXRhLmZyYW1lKG1hdHJpeChOQSwgcGFydF90aW1lcywgbGVuZ3RoKGNvbG5hbWVzKGdwc19kZl9jbGVhbikpLTIpKQogIGZvciAoaSBpbiAxOnBhcnRfdGltZXMpewogICAgdmFyX2RmW1tzdWJqXV1baSxdID0gZ3BzX2NsZWFuMl9ybXNzZF9mZWF0JHN1YmpfbWF0XzFbW3N1YmpdXVtbaV1dCiAgfQogIGNvbG5hbWVzKHZhcl9kZltbc3Vial1dKSA9IGNvbG5hbWVzKGdwc19kZl9jbGVhblszOjE3XSkKfQoKdmFyX3Bsb3RzID0gbGlzdCgpCmZvciAoZ3BzX2Z0IGluIGNvbG5hbWVzKGdwc19kZl9jbGVhblszOjE3XSkpewogIHZhcl9wbG90c1tbZ3BzX2Z0XV0gPSBoaXN0X2NoeChzYXBwbHkodmFyX2RmLCBmdW5jdGlvbihkZikgZGZbMSxncHNfZnRdKSwgdGl0bGUgPSBncHNfZnQsIHhheGlzID0gIkZlYXR1cmUgVmFyaWFiaWxpdHkoUk1TU0QpIiwgeWF4aXMgPSAiY291bnQiKQp9CgpSZWR1Y2UoYCtgLCB2YXJfcGxvdHMpCmBgYAoqKkZpZ3VyZSAxMDogRmVhdHVyZSB2YXJpYWJpbGl0eSBmb3IgYWxsIHN1YmplY3RzKiouIFZhcmlhYmlsaXR5IGlzIG1lYXN1cmVkIGJ5IGByb290IG1lYW4gc3F1YXJlIG9mIHN1Y2Nlc3NpdmUgZGlmZmVyZW5jZXNgIChSTVNTRCkuIFRoZSBoaXN0b2dyYW1zIGFyZSBmb3IgZGF0YSBpbiB0aGUgZmlyc3QgcmFuZG9tIGhhbGYgc3BsaXQuCgoKCiMjIyAxMC4gUHJlZGljdCB3aXRoIE5ldyBGZWF0dXJlcyAKYGBge3IgZGVmaW5lIG1hdGNoIGZ1bmN0aW9uIGZvciB2ZWN0b3IgZmVhdHVyZXN9CmNhbGNfbWF0Y2hfdmVjdG9yID0gZnVuY3Rpb24oc3Vial9tYXRfMSxzdWJqX21hdF8yLG1ldGhvZCkgewogIHBhcnRfdGltZXMgPSBsZW5ndGgoc3Vial9tYXRfMVtbMV1dKQogIGRhdGFiYXNlID0gbGlzdCgpIAogIGZvciAodGltZSBpbiAxOnBhcnRfdGltZXMpIHsKICAgICAgZGF0YWJhc2VbW3RpbWVdXSA9IGxhcHBseShzdWJqX21hdF8yLCBmdW5jdGlvbihzdWJqbWF0KSBzdWJqbWF0W1t0aW1lXV0pCiAgfQogIAogICAgIyBtYXRjaCB0YXJnZXQgdG8gZGF0YWJhc2UKICAgIG1hdGNoX2NvciA9IGxpc3QoKQogICAgZm9yIChzdWJqMSBpbiBuYW1lcyhzdWJqX21hdF8xKSl7ICNsb29wIHRocm91Z2ggZWFjaCBzdWJqCiAgICAgICMgY3JlYXRlIGEgbGlzdCBvZiB0YXJnZXQgYWNyb3NzIHBhcnRpdGlvbnMKICAgICAgdGFyZ2V0X2xpc3QgPSBsYXBwbHkoc3Vial9tYXRfMVtbc3ViajFdXSwgZnVuY3Rpb24ocGFydCkgcGFydCkKICAgICAgIyBjcmVhdGUgYSBtYXRjaCBsaXN0CiAgICAgIGZvciAodGltZSBpbiAxOnBhcnRfdGltZXMpewogICAgICAgIHRhcmdldF9zdWJqX3RpbWUgPSB0YXJnZXRfbGlzdFtbdGltZV1dICNsb29wIHRocm91Z2ggZWFjaCBwYXJ0aXRpb24KICAgICAgICBmb3IgKHN1YmoyIGluIG5hbWVzKHN1YmpfbWF0XzIpKXsgI2xvb3AgZXZlcnlvbmUgaW4gMm5kIGhhbGYKICAgICAgICAgIGRhdGFfc3Vial90aW1lID0gc3Vial9tYXRfMltbc3ViajJdXVtbdGltZV1dCiAgICAgICAgICBpZiAobWV0aG9kID09ICJjb3IiKSB7CiAgICAgICAgICBtYXRjaF9jb3JbW3N1YmoxXV1bW2FzLmNoYXJhY3Rlcih0aW1lKV1dW1tzdWJqMl1dID0gY29yKHRhcmdldF9zdWJqX3RpbWUsZGF0YV9zdWJqX3RpbWUsdXNlID0gIm5hLm9yLmNvbXBsZXRlIikKICAgICAgICAgIH0gCiAgICAgICAgICBlbHNlIGlmIChtZXRob2QgPT0gInJtc2UiKSB7CiAgICAgICAgICAgIG1hdGNoX2Nvcltbc3ViajFdXVtbYXMuY2hhcmFjdGVyKHRpbWUpXV1bW3N1YmoyXV0gPSBybXNlKHRhcmdldF9zdWJqX3RpbWUsZGF0YV9zdWJqX3RpbWUpCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CiAgICB9CiAgcmV0dXJuKG1hdGNoX2NvcikKfQpgYGAKCgpgYGB7ciBjYWxjIG1hdGNoIGFjY3VyYWN5IHVzaW5nIG1lYW4gZmVhdHVyZXN9Cm1hdGNoX21lYW5fcm1zZSA9IGNhbGNfbWF0Y2hfdmVjdG9yKGdwc19jbGVhbjJfbWVhbl9mZWF0JHN1YmpfbWF0XzEsZ3BzX2NsZWFuMl9tZWFuX2ZlYXQkc3Vial9tYXRfMiwicm1zZSIpCmFjY190aW1lX21lYW5fcm1zZSA9IGNhbGNfYWNjX3RpbWUobWF0Y2hfbWVhbl9ybXNlLHBhcnRfdGltZXMsIm1pbiIpCmFjY19zdWJqX21lYW5fcm1zZSA9IGNhbGNfYWNjX3N1YmoobWF0Y2hfbWVhbl9ybXNlLCBwYXJ0X3RpbWVzLCJtaW4iKQoKbWF0Y2hfbWVhbl9jb3IgPSBjYWxjX21hdGNoX3ZlY3RvcihncHNfY2xlYW4yX21lYW5fZmVhdCRzdWJqX21hdF8xLGdwc19jbGVhbjJfbWVhbl9mZWF0JHN1YmpfbWF0XzIsImNvciIpCmFjY190aW1lX21lYW5fY29yID0gY2FsY19hY2NfdGltZShtYXRjaF9tZWFuX2NvcixwYXJ0X3RpbWVzLCJtYXgiKQphY2Nfc3Vial9tZWFuX2NvciA9IGNhbGNfYWNjX3N1YmoobWF0Y2hfbWVhbl9jb3IsIHBhcnRfdGltZXMsIm1heCIpCmBgYAoKYGBge3IgY2FsYyBtYXRjaCBhY2N1cmFjeSB1c2luZyB2YXJpYWJpbGl0eSBmZWF0dXJlc30KbWF0Y2hfcm1zc2Rfcm1zZSA9IGNhbGNfbWF0Y2hfdmVjdG9yKGdwc19jbGVhbjJfcm1zc2RfZmVhdCRzdWJqX21hdF8xLGdwc19jbGVhbjJfcm1zc2RfZmVhdCRzdWJqX21hdF8yLCAicm1zZSIpCmFjY190aW1lX3Jtc3NkX3Jtc2UgPSBjYWxjX2FjY190aW1lKG1hdGNoX3Jtc3NkX3Jtc2UscGFydF90aW1lcywgIm1pbiIpCmFjY19zdWJqX3Jtc3NkX3Jtc2UgPSBjYWxjX2FjY19zdWJqKG1hdGNoX3Jtc3NkX3Jtc2UsIHBhcnRfdGltZXMsICJtaW4iKQoKCm1hdGNoX3Jtc3NkX2NvciA9IGNhbGNfbWF0Y2hfdmVjdG9yKGdwc19jbGVhbjJfcm1zc2RfZmVhdCRzdWJqX21hdF8xLGdwc19jbGVhbjJfcm1zc2RfZmVhdCRzdWJqX21hdF8yLCAiY29yIikKYWNjX3RpbWVfcm1zc2RfY29yID0gY2FsY19hY2NfdGltZShtYXRjaF9ybXNzZF9jb3IscGFydF90aW1lcywgIm1heCIpCmFjY19zdWJqX3Jtc3NkX2NvciA9IGNhbGNfYWNjX3N1YmoobWF0Y2hfcm1zc2RfY29yLCBwYXJ0X3RpbWVzLCAibWF4IikKYGBgCgojIyMgMTEuIFBlcm11dGF0aW9uIFRlc3RzIHdpdGggTmV3IEZlYXR1cmVzIAoKIyMjIyAxMWEuIEF2ZXJhZ2UgQWNjdXJhY3kKYGBge3IgZGVmaW5lIG5ldyBwZXJtdXRhdGlvbiBmdW5jdGlvbnN9CnBlcm1fdmVjdG9yID0gZnVuY3Rpb24oZ3BzX2RmLCBwZXJtX3RpbWUsIG1ldGhvZF9mZWF0dXJlLCBtZXRob2RfbWF0Y2gpewogIGdwc19kZl9wZXJtID0gZ3BzX2RmCiAgcGVybV9hY2NfdGltZSA9IGxpc3QoKQogIHBlcm1fYWNjX3N1YmogPSBsaXN0KCkKICBmb3IgKGkgaW4gMTpwZXJtX3RpbWUpIHsKICAgIHBlcm1fcGFydF90aW1lcyA9IDEKICAgIHByaW50KHBhc3RlKCJwcm9jZXNzaW5nIC4uLiIsIGksIi4uLiIpKQogICAgZ3BzX2RmX3Blcm0kSUlEID0gc2FtcGxlKGdwc19kZl9wZXJtJElJRCkKICAgIHBlcm1fc3Vial9zZXEgPSBtYWtlX3N1Ympfc2VxKGdwc19kZl9wZXJtLHBhcnRfdGltZXMgPSBwZXJtX3BhcnRfdGltZXMpCiAgICBpZiAobWV0aG9kX2ZlYXR1cmUgPT0gInJtc3NkIikgewogICAgICBwZXJtX2dwcyA9IG1ha2VfZmVhdHVyZV9ybXNzZChncHNfZGZfcGVybSxwZXJtX3N1Ympfc2VxKQogICAgfSBlbHNlIGlmIChtZXRob2RfZmVhdHVyZSA9PSAibWVhbiIpewogICAgICBwZXJtX2dwcyA9IG1ha2VfZmVhdHVyZV9tZWFuKGdwc19kZl9wZXJtLHBlcm1fc3Vial9zZXEpCiAgICB9CiAgICBwZXJtX21hdF8xID0gcGVybV9ncHMkc3Vial9tYXRfMQogICAgcGVybV9tYXRfMiA9IHBlcm1fZ3BzJHN1YmpfbWF0XzIKICAgIGlmIChtZXRob2RfbWF0Y2ggPT0gImNvciIpIHsKICAgICAgcGVybV9tYXRjaCA9IGNhbGNfbWF0Y2hfdmVjdG9yKHBlcm1fbWF0XzEscGVybV9tYXRfMiwgImNvciIpCiAgICAgIHBlcm1fYWNjX3RpbWVbW2ldXSA9IGNhbGNfYWNjX3RpbWUocGVybV9tYXRjaCxwYXJ0X3RpbWVzID0gcGVybV9wYXJ0X3RpbWVzLCAibWF4IikKICAgICAgcGVybV9hY2Nfc3VialtbaV1dID0gY2FsY19hY2Nfc3ViaihwZXJtX21hdGNoLHBhcnRfdGltZXMgPSBwZXJtX3BhcnRfdGltZXMsICJtYXgiKQogICAgfQogICAgZWxzZSBpZiAobWV0aG9kX21hdGNoID09ICJybXNlIil7CiAgICAgIHBlcm1fbWF0Y2ggPSBjYWxjX21hdGNoX3ZlY3RvcihwZXJtX21hdF8xLHBlcm1fbWF0XzIsICJybXNlIikKICAgICAgcGVybV9hY2NfdGltZVtbaV1dID0gY2FsY19hY2NfdGltZShwZXJtX21hdGNoLHBhcnRfdGltZXMgPSBwZXJtX3BhcnRfdGltZXMsICJtaW4iKQogICAgICBwZXJtX2FjY19zdWJqW1tpXV0gPSBjYWxjX2FjY19zdWJqKHBlcm1fbWF0Y2gscGFydF90aW1lcyA9IHBlcm1fcGFydF90aW1lcywgIm1pbiIpCiAgICB9CiAgICAKICB9CiAgcmV0dXJuKGxpc3QocGVybV9hY2NfdGltZT0gcGVybV9hY2NfdGltZSxwZXJtX2FjY19zdWJqID0gcGVybV9hY2Nfc3ViaikpCn0KYGBgCgoKYGBge3IgcnVuIHBlcm11dGF0aW9uIGZvciBtZWFuIGZlYXR1cmVzIG1hdGNoIGJ5IGNvciwgZmlnLndpZHRoPSA2LCBtZXNzYWdlPUZBTFNFfQpwZXJtX21lYW5fY29yID0gcGVybV92ZWN0b3IoZ3BzX2RmX2NsZWFuMiwgMTAwMCwgIm1lYW4iLCAiY29yIikKCnBfdGltZV9tZWFuX2NvciA9IGhpc3RfY2h4KGFjY190aW1lX21lYW5fY29yLCBiaW5zID0gOCwgdGl0bGUgPSBwYXN0ZSgiTWVhbiBGZWF0dXJlczogXG4gQXZlcmFnZSBBY2N1cmFjeSBBY3Jvc3MiLGxlbmd0aChhY2NfdGltZV9tZWFuKSwiRGF0YSBQYXJ0aXRpb25zIiksIHhheGlzID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiLCB5YXhpcyA9ICJDb3VudCIpCgpxX3RpbWVfbWVhbl9jb3IgPSAgaGlzdF9jaHgodW5saXN0KHBlcm1fbWVhbl9jb3IkcGVybV9hY2NfdGltZSksIGJpbnMgPSA4LCB0aXRsZSA9IHBhc3RlKCJNZWFuIEZlYXR1cmVzOiBcbiBBdmVyYWdlIEFjY3VyYWN5IEFjcm9zcyIsbGVuZ3RoKHBlcm1fbWVhbl9jb3IkcGVybV9hY2NfdGltZSksIlBlcm11dGF0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQoKcF90aW1lX21lYW5fY29yICsgcV90aW1lX21lYW5fY29yCmBgYAoqKkZpZ3VyZSAxMTogUHJlZGljdGlvbiBhY2N1cmFjeSB1c2luZyBtZWFuIGZlYXR1cmVzIGFuZCBtYXRjaGVkIGJ5IG1heCBwZWFyc29uIGNvcnJlbGF0aW9uKiouCgpgYGB7ciBydW4gcGVybXV0YXRpb24gZm9yIG1lYW4gZmVhdHVyZXMgbWF0Y2ggYnkgcm1zZSwgZmlnLndpZHRoPSA2LCBtZXNzYWdlPUZBTFNFfQpwZXJtX21lYW5fcm1zZSA9IHBlcm1fdmVjdG9yKGdwc19kZl9jbGVhbjIsIDEwMDAsICJtZWFuIiwgInJtc2UiKQoKcF90aW1lX21lYW5fcm1zZSA9IGhpc3RfY2h4KGFjY190aW1lX21lYW5fcm1zZSwgYmlucyA9IDgsIHRpdGxlID0gcGFzdGUoIk1lYW4gRmVhdHVyZXM6IFxuIEF2ZXJhZ2UgQWNjdXJhY3kgQWNyb3NzIixsZW5ndGgoYWNjX3RpbWVfbWVhbiksIkRhdGEgUGFydGl0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQoKcV90aW1lX21lYW5fcm1zZSA9ICBoaXN0X2NoeCh1bmxpc3QocGVybV9tZWFuX3Jtc2UkcGVybV9hY2NfdGltZSksIGJpbnMgPSA4LCB0aXRsZSA9IHBhc3RlKCJNZWFuIEZlYXR1cmVzOiBcbiBBdmVyYWdlIEFjY3VyYWN5IEFjcm9zcyIsbGVuZ3RoKHBlcm1fbWVhbl9ybXNlJHBlcm1fYWNjX3RpbWUpLCJQZXJtdXRhdGlvbnMiKSwgeGF4aXMgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIsIHlheGlzID0gIkNvdW50IikKCnBfdGltZV9tZWFuX3Jtc2UgKyBxX3RpbWVfbWVhbl9ybXNlCmBgYAoqKkZpZ3VyZSAxMTogUHJlZGljdGlvbiBhY2N1cmFjeSB1c2luZyBtZWFuIGZlYXR1cmVzIGFuZCBtYXRjaGVkIGJ5IG1pbiByb290IG1lYW4gc3F1YXJlZCoqLgoKCmBgYHtyIHJ1biBwZXJtdXRhdGlvbiBmb3Igcm1zc2QgZmVhdHVyZXMgbWF0Y2hlZCBieSBjb3IsIGZpZy53aWR0aD0gNiwgbWVzc2FnZT1GQUxTRX0KcGVybV9ybXNzZF9jb3IgPSBwZXJtX3ZlY3RvcihncHNfZGZfY2xlYW4yLCAxMDAwLCAicm1zc2QiLCJjb3IiKQoKcF90aW1lX3Jtc3NkX2NvciA9IGhpc3RfY2h4KGFjY190aW1lX3Jtc3NkX2NvciwgYmlucyA9IDgsIHRpdGxlID0gcGFzdGUoIlJNU1NEIEZlYXR1cmVzOiBcbiBBdmVyYWdlIEFjY3VyYWN5IEFjcm9zcyIsbGVuZ3RoKGFjY190aW1lX3Jtc3NkX2NvciksIkRhdGEgUGFydGl0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQoKcV90aW1lX3Jtc3NkX2NvciA9IGhpc3RfY2h4KHVubGlzdChwZXJtX3Jtc3NkX2NvciRwZXJtX2FjY190aW1lKSwgYmlucyA9IDgsIHRpdGxlID0gcGFzdGUoIlJNU1NEIEZlYXR1cmVzOiBBdmVyYWdlIEFjY3VyYWN5IEFjcm9zcyIsbGVuZ3RoKHBlcm1fcm1zc2RfY29yJHBlcm1fYWNjX3RpbWVfY29yKSwiUGVybXV0YXRpb25zIiksIHhheGlzID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiLCB5YXhpcyA9ICJDb3VudCIpCgpwX3RpbWVfcm1zc2RfY29yICsgcV90aW1lX3Jtc3NkX2NvcgpgYGAKKipGaWd1cmUgMTI6IFByZWRpY3Rpb24gYWNjdXJhY3kgdXNpbmcgUk1TU0QgZmVhdHVyZXMgYW5kIG1hdGNoZWQgYnkgbWF4IHBlYXJzb24gY29ycmVsYXRpb24qKi4KCmBgYHtyIHJ1biBwZXJtdXRhdGlvbiBmb3Igcm1zc2QgZmVhdHVyZXMgbWF0Y2hlZCBieSBybXNlLCBmaWcud2lkdGg9IDYsIG1lc3NhZ2U9RkFMU0V9CnBlcm1fcm1zc2Rfcm1zZSA9IHBlcm1fdmVjdG9yKGdwc19kZl9jbGVhbjIsIDEwMDAsICJybXNzZCIsInJtc2UiKQoKcF90aW1lX3Jtc3NkX3Jtc2UgPSBoaXN0X2NoeChhY2NfdGltZV9ybXNzZF9ybXNlLCBiaW5zID0gOCwgdGl0bGUgPSBwYXN0ZSgiUk1TU0QgRmVhdHVyZXM6IFxuIEF2ZXJhZ2UgQWNjdXJhY3kgQWNyb3NzIixsZW5ndGgoYWNjX3RpbWVfcm1zc2RfY29yKSwiRGF0YSBQYXJ0aXRpb25zIiksIHhheGlzID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiLCB5YXhpcyA9ICJDb3VudCIpCgpxX3RpbWVfcm1zc2Rfcm1zZSA9IGhpc3RfY2h4KHVubGlzdChwZXJtX3Jtc3NkX3Jtc2UkcGVybV9hY2NfdGltZSksIGJpbnMgPSA4LCB0aXRsZSA9IHBhc3RlKCJSTVNTRCBGZWF0dXJlczogQXZlcmFnZSBBY2N1cmFjeSBBY3Jvc3MiLGxlbmd0aChwZXJtX3Jtc3NkX3Jtc2UkcGVybV9hY2NfdGltZV9jb3IpLCJQZXJtdXRhdGlvbnMiKSwgeGF4aXMgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIsIHlheGlzID0gIkNvdW50IikKCnBfdGltZV9ybXNzZF9ybXNlICsgcV90aW1lX3Jtc3NkX3Jtc2UKYGBgCioqRmlndXJlIDEzOiBQcmVkaWN0aW9uIGFjY3VyYWN5IHVzaW5nIFJNU1NEIGZlYXR1cmVzIGFuZCBtYXRjaGVkIGJ5IG1pbiByb290IG1lYW4gc3F1YXJlZCoqLgoKYGBge3IgZGVmaW5lIGNvbWJpbmUgcGVybSBzdWJqIGZ1bmN0aW9uc30KCmNvbWJpbmVfcGVybV9zdWJqID0gZnVuY3Rpb24oYWNjX3N1YmpfcGxvdCwgcGVybV9hY2Nfc3ViaikgewogIHBlcm1fc3ViaiA9IGxpc3QoKQogIGZvciAoc3ViaiBpbiBhY2Nfc3Vial9wbG90JGRhdGEkeCkgewogICAgcGVybV9zdWJqJHZhbFtbc3Vial1dID0gc2FwcGx5KHBlcm1fYWNjX3N1YmosIGZ1bmN0aW9uKHBlcm0pIHBlcm1bd2hpY2gobmFtZXMocGVybSkgPT0gc3ViaildKQogICAgcGVybV9zdWJqJGhpc3RbW3N1YmpdXSA9IGhpc3RfY2h4KHBlcm1fc3Vialtbc3Vial1dJHZhbCwgYmlucyA9IDgsIHRpdGxlID0gcGFzdGUoc3ViaiwiOiBBY2N1cmFjeSBBY3Jvc3MgXG4iLHBlcm1fdGltZSwiUGVybXV0YXRpb25zIiksIHhheGlzID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiLCB5YXhpcyA9ICJDb3VudCIpCiAgICBwZXJtX3N1YmokYWNjW1tzdWJqXV0gPSBzdW0ocGVybV9zdWJqJHZhbFtbc3Vial1dKS9wZXJtX3RpbWUKICB9CiAgcmV0dXJuKHBlcm1fc3ViaikKfQoKCnN1Ympfc2NhdHRlcl9wZXJtID0gZnVuY3Rpb24oZ3BzX2RmLGFjY19zdWJqLHBlcm1fc3ViaiwgbWV0aG9kKXsKICAgIHN1YmpfZGYgPC0gZGF0YS5mcmFtZSh4PXVuaXF1ZShncHNfZGYkSUlEKSkKICBzdWJqX2RmJHkgPSBhY2Nfc3ViagogIHN1YmpfZGYkeV9wZXJtID0gdW5saXN0KHBlcm1fc3ViaiRhY2MpCiAgc3Vial9kZiA9IHN1YmpfZGZbb3JkZXIoc3Vial9kZiR5KSxdCiAgcCA9IGdncGxvdChzdWJqX2RmLCBhZXMoeCA9IHJlb3JkZXIoeCwgeSksIHkgPSB2YWx1ZSkpICsgCiAgICBnZW9tX3BvaW50KGFlcyh5ID0geSwgY29sID0gInN1YmplY3QgZGF0YSIpKSArIAogICAgZ2VvbV9wb2ludChhZXMoeSA9IHlfcGVybSwgY29sID0gInBlcm11dGF0aW9uIikpICsKICAgIHRoZW1lX2Nvd3Bsb3QoKSArIAogICAgbGFicyh0aXRsZSA9IHBhc3RlKG1ldGhvZCwiRmVhdHVyZXMgXG4gU3ViamVjdCBMZXZlbCBBY2N1cmFjeSBBY3Jvc3MiLHBhcnRfdGltZXMsIkRhdGEgUGFydGl0aW9ucyIpLCAKICAgICAgICAgeCA9ICJTdWJqZWN0cyIsIHkgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZSA9IDgpLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKICByZXR1cm4ocCkKfQoKYGBgCgoKYGBge3IgbWVhbiBmZWF0dXJlcyBieSBzdWJqZWN0IHdpdGggcGVybXV0YXRpb24sIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9CnBfc3Vial9tZWFuID0gc3Vial9zY2F0dGVyKGdwc19kZl9jbGVhbjIsIGFjY19zdWJqX21lYW4sICJNZWFuIikKcGVybV9zdWJqX21lYW4gPSBjb21iaW5lX3Blcm1fc3ViaihwX3N1YmpfbWVhbixwZXJtX21lYW4kcGVybV9hY2Nfc3ViaikKcF9zdWJqX21lYW5fcGVybSA9IHN1Ympfc2NhdHRlcl9wZXJtKGdwc19kZl9jbGVhbjIsIGFjY19zdWJqX21lYW4sIHBlcm1fc3Vial9tZWFuLCAiTWVhbiIpCmdncGxvdGx5KHBfc3Vial9tZWFuX3Blcm0pCmBgYAoqKkZpZ3VyZSAxNDogU3ViamVjdCBsZXZlbCBwcmVkaWN0aW9uIGFjY3VyYWN5IHVzaW5nIG1lYW4gZmVhdHVyZXMgYW5kIG1hdGNoZWQgYnkgbWF4IHBlYXJzb24gY29ycmVsYXRpb24qKi4KCmBgYHtyIHJtc3NkIGZlYXR1cmVzIGJ5IHN1YmplY3Qgd2l0aCBwZXJtdXRhdGlvbiwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KcF9zdWJqX3Jtc3NkID0gc3Vial9zY2F0dGVyKGdwc19kZl9jbGVhbjIsIGFjY19zdWJqX3Jtc3NkLCAiUk1TU0QiKQpwZXJtX3N1Ympfcm1zc2QgPSBjb21iaW5lX3Blcm1fc3ViaihwX3N1Ympfcm1zc2QscGVybV9ybXNzZCRwZXJtX2FjY19zdWJqKQpwX3N1Ympfcm1zc2RfcGVybSA9IHN1Ympfc2NhdHRlcl9wZXJtKGdwc19kZl9jbGVhbjIsIGFjY19zdWJqX3Jtc3NkLCBwZXJtX3N1Ympfcm1zc2QsICJSTVNTRCIpCmdncGxvdGx5KHBfc3Vial9ybXNzZF9wZXJtKQpgYGAKKipGaWd1cmUgMTU6IFN1YmplY3QgbGV2ZWwgcHJlZGljdGlvbiBhY2N1cmFjeSB1c2luZyBSTVNTRCBmZWF0dXJlcyBhbmQgbWF0Y2hlZCBieSBtYXggcGVhcnNvbiBjb3JyZWxhdGlvbioqLgoKCiMjIyAxMi4gQ29tcGFyZSBGZWF0dXJlcwpgYGB7ciBjb21wYXJlIGFjcm9zcyBkYXRhIHBhcnRpdGlvbiwgZmlnLndpZHRoPTh9CnBfdGltZV9jb3IgKyBwX3RpbWVfbWVhbiArIHBfdGltZV9ybXNzZApgYGAKKipGaWd1cmUgMTY6IFByZWRpY3Rpb24gYWNjdXJhY3kgYWNyb3NzIHRocmVlIGZlYXR1cmUgc2V0cyoqLgoKYGBge3IgY29tcGFyZSBhY3Jvc3Mgc3ViamVjdHMsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTR9CnBfc3Vial9jb3JfcGVybSAvIHBfc3Vial9tZWFuX3Blcm0gLyBwX3N1Ympfcm1zc2RfcGVybQpgYGAKKipGaWd1cmUgMTc6IFN1YmplY3QgbGV2ZWwgcHJlZGljdGlvbiBhY2N1cmFjeSBhY3Jvc3MgdGhyZWUgZmVhdHVyZSBzZXRzKiouCgoKIyMjIDEzLiBDb25mb3VuZGluZyB2YXJpYWJsZXMKYGBge3Igc3ViaiBhY2N1cmFjeSBjb3Igd2l0aCBwb3RlbnRpYWwgY29uZm91bmRlcnMsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTcsIHdtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpjb25mX3NjYXR0ZXJfcGxvdCA9IGZ1bmN0aW9uKGNvbmZfdmFyLCBhY2Nfc3ViaiwgY29sKXsKICBpZiAoIWlkZW50aWNhbChjb25mX3ZhciRJSUQsbmFtZXMoYWNjX3N1YmopKSkgewogICAgc3RvcCgic3ViaiBuYW1lcyBkbyBub3QgbWF0Y2giKQogIH0KICBjb25mX2RmID0gZGF0YS5mcmFtZShjb25mID0gdW5saXN0KGNvbmZfdmFyWyxjb2xdKSwgYWNjX3N1YmogPSBhY2Nfc3ViaikKICBjb25mX3NjYXR0ZXIgPSBnZ3NjYXR0ZXIoY29uZl9kZiwgeSA9ICJhY2Nfc3ViaiIsIHggPSAiY29uZiIsCiAgIGFkZCA9ICJyZWcubGluZSIsICAjIEFkZCByZWdyZXNzaW4gbGluZQogICBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJibGFjayIsIGZpbGwgPSAibGlnaHRncmF5IiksICMgQ3VzdG9taXplIHJlZy4gbGluZQogICBjb25mLmludCA9IFRSVUUgIyBBZGQgY29uZmlkZW5jZSBpbnRlcnZhbAogICApICsgc3RhdF9jb3IobWV0aG9kID0gInBlYXJzb24iKSArCiAgICB0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgbGFicyh0aXRsZSA9IHBhc3RlKGNvbCksIHkgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIsIHggPSBjb2wpCiAgcmV0dXJuKGNvbmZfc2NhdHRlcikKICB9CgpzdWJqX21pbm1pc3NpbmcgPSBncHNfZGZfY2xlYW4yICU+JSBncm91cF9ieShJSUQpICU+JSBzdW1tYXJpc2VfaWYoaXMubnVtZXJpYywgbWVhbiwgbmEucm0gPSBUUlVFKQpzcF9wbG90cyA9IGxpc3QoKQpmb3IgKGdwc19mdCBpbiBjb2xuYW1lcyhzdWJqX21pbm1pc3NpbmcpWy0xXSl7CiAgc3BfcGxvdHNbW2dwc19mdF1dID0gY29uZl9zY2F0dGVyX3Bsb3Qoc3Vial9taW5taXNzaW5nLGFjY19zdWJqLGdwc19mdCkKfQoKUmVkdWNlKGArYCwgc3BfcGxvdHMpCmBgYAoqKkZpZ3VyZSAxODogUmVsYXRpb25zaGlwcyBiZXR3ZWVuIHN1YmplY3QgbGV2ZWwgcHJlZGljdGlvbiBhY2N1cmFjeSBhbmQgaW5kaXZpZHVhbCBmZWF0dXJlcyoqLiBOb3RhYmx5LCB0aGVyZSB3YXMgbm8gcmVsYXRpb25zaGlwIGJldHdlZW4gaW5kaXZpZHVhbCBkYXRhIG1pc3NpbmduZXNzIGFuZCBzdWJqZWN0IGxldmVsIHByZWRpY3Rpb24gYWNjdXJhY3kuCgoKYGBge3IgYWNjdXJhY3kgZXhwbGFpbmVkIGJ5IGRhdGEgYW1vdW50fQpzdWJqX2RheXMgPSBncHNfZGZfY2xlYW4yICU+JSBncm91cF9ieShJSUQpICU+JSBkcGx5cjo6dGFsbHkobmFtZSA9ICJEYXlzIENvbGxlY3RlZCIpCmNvbmZfc2NhdHRlcl9wbG90KHN1YmpfZGF5cywgYWNjX3N1YmosICJEYXlzIENvbGxlY3RlZCIpCmBgYAoqKkZpZ3VyZSAxOTogUmVsYXRpb25zaGlwcyBiZXR3ZWVuIHN1YmplY3QgbGV2ZWwgcHJlZGljdGlvbiBhY2N1cmFjeSBhbmQgZGF0YSBxdWFudGl0eSoqLiAKCgojIyMgMTQuIENvbWJpbmUgRmVhdHVyZXMKYGBge3IgY29tYmluZSBmZWF0dXJlc30KCmdwc19jbGVhbjJfY29tYmluZWRfZmVhdCA9IGxpc3QoKQpmb3IgKHN1YmogaW4gbmFtZXMoc3Vial9tYXRfMSkpewogIGZvciAodGltZSBpbiAxOnBhcnRfdGltZXMpewogICAgY29yX2ZlYXQxID0gZ3BzX2NsZWFuMl9mZWF0dXJlJHN1YmpfbWF0XzFbW3N1YmpdXVtbdGltZV1dJGNvcgogICAgbWVhbl9mZWF0MSA9IGdwc19jbGVhbjJfbWVhbl9mZWF0JHN1YmpfbWF0XzFbW3N1YmpdXVtbdGltZV1dCiAgICBybXNzZF9mZWF0MSA9IGdwc19jbGVhbjJfcm1zc2RfZmVhdCRzdWJqX21hdF8xW1tzdWJqXV1bW3RpbWVdXQogICAgCiAgICBjb3JfZmVhdDIgPSBncHNfY2xlYW4yX2ZlYXR1cmUkc3Vial9tYXRfMltbc3Vial1dW1t0aW1lXV0kY29yCiAgICBtZWFuX2ZlYXQyID0gZ3BzX2NsZWFuMl9tZWFuX2ZlYXQkc3Vial9tYXRfMltbc3Vial1dW1t0aW1lXV0KICAgIHJtc3NkX2ZlYXQyID0gZ3BzX2NsZWFuMl9ybXNzZF9mZWF0JHN1YmpfbWF0XzJbW3N1YmpdXVtbdGltZV1dCiAgICAKICAgIGdwc19jbGVhbjJfY29tYmluZWRfZmVhdCRzdWJqX21hdF8xW1tzdWJqXV1bW3RpbWVdXSA9IGMoY29yX2ZlYXQxLCBtZWFuX2ZlYXQxLCBybXNzZF9mZWF0MSkKICAgIGdwc19jbGVhbjJfY29tYmluZWRfZmVhdCRzdWJqX21hdF8yW1tzdWJqXV1bW3RpbWVdXSA9IGMoY29yX2ZlYXQyLCBtZWFuX2ZlYXQyLCBybXNzZF9mZWF0MikKICB9Cn0KCmBgYAoKYGBge3IgbWF0Y2ggd2l0aCBjb21iaW5lZCBmZWF0dXJlcywgZmlnLmFsaWduPSJjZW50ZXIifQptYXRjaF9jb21iaW5lZF9mZWF0ID0gY2FsY19tYXRjaF92ZWN0b3IoZ3BzX2NsZWFuMl9jb21iaW5lZF9mZWF0JHN1YmpfbWF0XzEsIGdwc19jbGVhbjJfY29tYmluZWRfZmVhdCRzdWJqX21hdF8yKQphY2NfdGltZV9jYiAgPSBjYWxjX2FjY190aW1lKG1hdGNoX2NvbWJpbmVkX2ZlYXQsIHBhcnRfdGltZXMpCmFjY19zdWJqX2NiICA9IGNhbGNfYWNjX3N1YmoobWF0Y2hfY29tYmluZWRfZmVhdCwgcGFydF90aW1lcykKcCA9IGhpc3RfY2h4KGFjY190aW1lX2NiLCBiaW5zID0gOCwgdGl0bGUgPSBwYXN0ZSgiQ29tYmluZWQgRmVhdHVyZXMgXG4gYnkgZGF0YSBwYXJ0aXRpb24iKSwgeGF4aXMgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIsIHlheGlzID0gIkNvdW50IikKcSA9IGhpc3RfY2h4KGFjY19zdWJqX2NiLCBiaW5zID0gOCwgdGl0bGUgPSBwYXN0ZSgiQ29tYmluZWQgRmVhdHVyZXMgXG4gYnkgc3ViamVjdCIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQpwICsgcQpgYGAKKipGaWd1cmUgMjA6IFByZWRpY3Rpb24gYWNjdXJhY3kgd2hlbiBhbGwgdGhyZWUgc2V0cyBvZiBmZWF0dXJlcyB3ZXJlIGNvbWJpbmVkKiouIAoKCiMjIyAxNS4gQXNzb2NpYXRpb25zIHdpdGggUHN5Y2hvcGF0aG9sb2d5CmBgYHtyIHBzeWNob3BhdGhvbG9neSAsIGZpZy5hbGlnbiA9ICJjZW50ZXIiLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQppZHMgPSByZWFkLnhsc3goZmlsZS5wYXRoKHByb2plY3RfcGF0aCwiZGF0YS9zdWJqZWN0dHJhY2tlcl80Lnhsc3giKSlbMTpsZW5ndGgodW5pcXVlKGdwc19kZl9jbGVhbjIkSUlEKSksXQpwc3ljaF9zY29yZSA9IHJlYWQuY3N2KGZpbGUucGF0aChwcm9qZWN0X3BhdGgsImRhdGEvc2VsZl9yZXBvcnRfc2NvcmVkXzIwMjAwMTI4LmNzdiIpKQojIHBzeWNoX3BybyA9IHBzeWNoICU+JSBmaWx0ZXIoYXJpX3Byb2JhbmRfY29tcGxldGUgPT0gMikKcHN5Y2hfYmVpd2UgPSBpbm5lcl9qb2luKGlkcyxwc3ljaF9zY29yZSwgYnkgPSBjKCJCQkxJRCIgPSAiYmJsaWQiKSkKYWNjX3N1YmpfZGYgPSBkYXRhLmZyYW1lKGJlaXdlSUQgPSBuYW1lcyhhY2Nfc3ViaiksIGFjYyA9IGFjY19zdWJqKQpwc3ljaF9iZWl3ZV9hY2Nfc3ViaiA9IGlubmVyX2pvaW4oYWNjX3N1YmpfZGYscHN5Y2hfYmVpd2UsIGJ5ID0gImJlaXdlSUQiKQoKcHN5Y2hfaXRlbSA9IHJlYWQuY3N2KGZpbGUucGF0aChwcm9qZWN0X3BhdGgsImRhdGEvc2VsZl9yZXBvcnRfaXRlbXdpc2UuY3N2IikpCgpwc3ljaF9zdW0gPSBwc3ljaF9iZWl3ZV9hY2Nfc3ViaiAlPiUKICAgICAgICAgICAgbXV0YXRlKHN1bV9hbHMgPSByb3dTdW1zKC5bZ3JlcCgiXmFsc19bMC05XSQiLGNvbG5hbWVzKHBzeWNoX2JlaXdlX2FjY19zdWJqKSldLCBuYS5ybSA9IFQpKSAlPiUKICAgICAgICAgICAgbXV0YXRlKHN1bV9hcmkgPSByb3dTdW1zKC5bZ3JlcCgiXmFyaV9bMC05XSQiLGNvbG5hbWVzKHBzeWNoX2JlaXdlX2FjY19zdWJqKSldLCBuYS5ybSA9IFQpKQoKcHN5Y2hfc3AgPSBmdW5jdGlvbihwc3ljaF9kZixwc3ljaF92YXIsIHhsYWIpIHsKIHNwID0gZ2dzY2F0dGVyKHBzeWNoX2RmLCB5ID0gcHN5Y2hfdmFyLCB4ID0gImFjYyIsCiAgIGFkZCA9ICJyZWcubGluZSIsICAjIEFkZCByZWdyZXNzaW4gbGluZQogICBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJibGFjayIsIGZpbGwgPSAibGlnaHRncmF5IiksICMgQ3VzdG9taXplIHJlZy4gbGluZQogICBjb25mLmludCA9IFRSVUUgIyBBZGQgY29uZmlkZW5jZSBpbnRlcnZhbAogICApICsgc3RhdF9jb3IobWV0aG9kID0gInBlYXJzb24iKSArCiAgICB0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgbGFicyh4ID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiLCB5ID0geGxhYiwgdGl0bGUgPSB4bGFiKQogIHJldHVybihzcCkKfQoKc3BfYWxzID0gcHN5Y2hfc3AocHN5Y2hfYmVpd2VfYWNjX3N1YmosICdhbHNfc2NvcmVfYXZnJywiQWZmZWN0aXZlIExhYmlsaXR5IFNjb3JlIChBTFMpIikKc3BfYXJpID0gcHN5Y2hfc3AocHN5Y2hfYmVpd2VfYWNjX3N1YmosICdhcmlfc2NvcmVfdG90YWwnLCJBZmZlY3RpdmUgUmVhY3Rpdml0eSBJbmRleCAoQVJJKSIpCgpzcF9hbHMgKyBzcF9hcmkKCmBgYAoqKkZpZ3VyZSAyMTogQXNzb2NpYXRpb25zIGJldHdlZW4gaXJyaXRhYmlsaXR5IGFuZCBwcmVkaWN0aW9uIGFjY3VyYWN5KiouIAoKIyMjIDE2LiBBc3NvY2lhdGlvbnMgd2l0aCBDb2duaXRpb24KYGBge3IgcHN5Y2hvcGF0aG9sb2d5LCBmaWcuYWxpZ249ImNlbnRlciIsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNuYl9zY29yZSA9IHJlYWQuY3N2KGZpbGUucGF0aChwcm9qZWN0X3BhdGgsImRhdGEvZ3JtcHlfYmlmYWN0b3JfY25iX2NsZWFuZWQuY3N2IikpCmNvcl90cmFpdHMgPSByZWFkLmNzdihmaWxlLnBhdGgocHJvamVjdF9wYXRoLCJkYXRhL2dybXB5X2NvcnJ0cmFpdHNfY25iX2NsZWFuZWQuY3N2IikpCmNuYl9iZWl3ZSA9IGlubmVyX2pvaW4oaWRzLGNuYl9zY29yZSwgYnkgPSBjKCJCQkxJRCIgPSAiYmJsaWQiKSkKCmBgYAoKCiMjIyAxNy4gTWlzYyBJbmZvCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiNzZXNzaW9uSW5mbygpCmBgYAoK
A work by Cedric Huchuan Xia
hxia@upenn.edu